@qlover/create-app 0.0.1
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 +10 -0
- package/bin/create-app.js +28 -0
- package/dist/cjs/index.d.ts +67 -0
- package/dist/cjs/index.js +1 -0
- package/dist/es/index.d.ts +67 -0
- package/dist/es/index.js +1 -0
- package/package.json +60 -0
- package/templates/fe-react/.env +3 -0
- package/templates/fe-react/README.md +50 -0
- package/templates/fe-react/config/app.common.ts +52 -0
- package/templates/fe-react/config/app.router.json +150 -0
- package/templates/fe-react/config/feapi.mock.json +14 -0
- package/templates/fe-react/config/i18n.ts +21 -0
- package/templates/fe-react/config/theme.json +90 -0
- package/templates/fe-react/eslint.config.js +31 -0
- package/templates/fe-react/index.html +13 -0
- package/templates/fe-react/lib/fe-react-controller/FeController.ts +15 -0
- package/templates/fe-react/lib/fe-react-controller/index.ts +2 -0
- package/templates/fe-react/lib/fe-react-controller/useController.ts +71 -0
- package/templates/fe-react/lib/fe-react-theme/ThemeController.ts +40 -0
- package/templates/fe-react/lib/fe-react-theme/ThemeStateGetter.ts +53 -0
- package/templates/fe-react/lib/fe-react-theme/index.ts +3 -0
- package/templates/fe-react/lib/fe-react-theme/tw-generator.js +239 -0
- package/templates/fe-react/lib/fe-react-theme/type.ts +21 -0
- package/templates/fe-react/lib/openAiApi/OpenAIAuthPlugin.ts +29 -0
- package/templates/fe-react/lib/openAiApi/OpenAIClient.ts +51 -0
- package/templates/fe-react/lib/openAiApi/StreamProcessor.ts +81 -0
- package/templates/fe-react/lib/openAiApi/index.ts +3 -0
- package/templates/fe-react/lib/request-common-plugin/index.ts +169 -0
- package/templates/fe-react/lib/tw-root10px/index.css +4 -0
- package/templates/fe-react/lib/tw-root10px/index.js +178 -0
- package/templates/fe-react/package.json +49 -0
- package/templates/fe-react/postcss.config.js +6 -0
- package/templates/fe-react/public/locales/en/about.json +3 -0
- package/templates/fe-react/public/locales/en/common.json +6 -0
- package/templates/fe-react/public/locales/en/executor.json +6 -0
- package/templates/fe-react/public/locales/en/home.json +10 -0
- package/templates/fe-react/public/locales/en/jsonStorage.json +11 -0
- package/templates/fe-react/public/locales/en/login.json +7 -0
- package/templates/fe-react/public/locales/en/request.json +15 -0
- package/templates/fe-react/public/locales/zh/about.json +3 -0
- package/templates/fe-react/public/locales/zh/common.json +7 -0
- package/templates/fe-react/public/locales/zh/executor.json +7 -0
- package/templates/fe-react/public/locales/zh/home.json +10 -0
- package/templates/fe-react/public/locales/zh/jsonStorage.json +11 -0
- package/templates/fe-react/public/locales/zh/login.json +8 -0
- package/templates/fe-react/public/locales/zh/request.json +15 -0
- package/templates/fe-react/public/logo.svg +1 -0
- package/templates/fe-react/src/App.tsx +20 -0
- package/templates/fe-react/src/assets/react.svg +1 -0
- package/templates/fe-react/src/components/Loading.tsx +41 -0
- package/templates/fe-react/src/components/LocaleLink.tsx +42 -0
- package/templates/fe-react/src/components/ProcessProvider.tsx +41 -0
- package/templates/fe-react/src/components/ThemeSwitcher.tsx +29 -0
- package/templates/fe-react/src/containers/context/BaseRouteContext.ts +27 -0
- package/templates/fe-react/src/containers/context/BaseRouteProvider.tsx +11 -0
- package/templates/fe-react/src/containers/globals.ts +31 -0
- package/templates/fe-react/src/containers/index.ts +71 -0
- package/templates/fe-react/src/hooks/useLanguageGuard.ts +25 -0
- package/templates/fe-react/src/hooks/useStrictEffect.ts +29 -0
- package/templates/fe-react/src/main.tsx +15 -0
- package/templates/fe-react/src/pages/404.tsx +14 -0
- package/templates/fe-react/src/pages/500.tsx +14 -0
- package/templates/fe-react/src/pages/auth/Layout.tsx +14 -0
- package/templates/fe-react/src/pages/auth/Login.tsx +62 -0
- package/templates/fe-react/src/pages/auth/Register.tsx +3 -0
- package/templates/fe-react/src/pages/base/About.tsx +12 -0
- package/templates/fe-react/src/pages/base/Executor.tsx +38 -0
- package/templates/fe-react/src/pages/base/Home.tsx +78 -0
- package/templates/fe-react/src/pages/base/JSONStorage.tsx +124 -0
- package/templates/fe-react/src/pages/base/Layout.tsx +17 -0
- package/templates/fe-react/src/pages/base/RedirectPathname.tsx +15 -0
- package/templates/fe-react/src/pages/base/Request.tsx +91 -0
- package/templates/fe-react/src/pages/base/components/BaseHeader.tsx +19 -0
- package/templates/fe-react/src/pages/index.tsx +108 -0
- package/templates/fe-react/src/services/controllers/ExecutorController.ts +56 -0
- package/templates/fe-react/src/services/controllers/JSONStorageController.ts +42 -0
- package/templates/fe-react/src/services/controllers/RequestController.ts +105 -0
- package/templates/fe-react/src/services/controllers/RouterController.ts +90 -0
- package/templates/fe-react/src/services/controllers/UserController.ts +146 -0
- package/templates/fe-react/src/services/feApi/FeApi.ts +51 -0
- package/templates/fe-react/src/services/feApi/FeApiMockPlugin.ts +42 -0
- package/templates/fe-react/src/services/feApi/FeApiType.ts +55 -0
- package/templates/fe-react/src/services/feApi/index.ts +2 -0
- package/templates/fe-react/src/services/i18n/index.ts +50 -0
- package/templates/fe-react/src/services/pageProcesser/PageProcesser.ts +29 -0
- package/templates/fe-react/src/services/pageProcesser/index.ts +1 -0
- package/templates/fe-react/src/styles/css/index.css +2 -0
- package/templates/fe-react/src/styles/css/page.css +3 -0
- package/templates/fe-react/src/styles/css/tailwind.css +3 -0
- package/templates/fe-react/src/types/Page.ts +49 -0
- package/templates/fe-react/src/types/UIDependenciesInterface.ts +31 -0
- package/templates/fe-react/src/types/global.d.ts +7 -0
- package/templates/fe-react/src/utils/RequestLogger.ts +34 -0
- package/templates/fe-react/src/utils/datetime.ts +25 -0
- package/templates/fe-react/src/utils/thread.ts +3 -0
- package/templates/fe-react/src/vite-env.d.ts +1 -0
- package/templates/fe-react/tailwind.config.js +18 -0
- package/templates/fe-react/tsconfig.app.json +29 -0
- package/templates/fe-react/tsconfig.json +7 -0
- package/templates/fe-react/tsconfig.node.json +22 -0
- package/templates/fe-react/vite.config.ts +32 -0
- package/templates/pack-app/.editorconfig +23 -0
- package/templates/pack-app/.env +5 -0
- package/templates/pack-app/.env.template +6 -0
- package/templates/pack-app/.gitattributes +2 -0
- package/templates/pack-app/.github/workflows/general-check.yml +50 -0
- package/templates/pack-app/.github/workflows/release.yml.template +110 -0
- package/templates/pack-app/.prettierignore +5 -0
- package/templates/pack-app/.prettierrc.js +3 -0
- package/templates/pack-app/CHANGELOG.md +0 -0
- package/templates/pack-app/README.md +1 -0
- package/templates/pack-app/eslint.config.js +77 -0
- package/templates/pack-app/fe-config.json +10 -0
- package/templates/pack-app/jest.config.js +31 -0
- package/templates/pack-app/package.json +66 -0
- package/templates/pack-app/packages/browser/__tests__/calc.test.ts +9 -0
- package/templates/pack-app/packages/browser/package.json +11 -0
- package/templates/pack-app/packages/browser/rollup.config.js +70 -0
- package/templates/pack-app/packages/browser/src/calc.ts +3 -0
- package/templates/pack-app/packages/browser/src/index.ts +1 -0
- package/templates/pack-app/packages/browser/tsconfig.json +15 -0
- package/templates/pack-app/packages/node/__tests__/readJson.test.ts +25 -0
- package/templates/pack-app/packages/node/package.json +11 -0
- package/templates/pack-app/packages/node/rollup.config.js +89 -0
- package/templates/pack-app/packages/node/src/index.ts +7 -0
- package/templates/pack-app/packages/node/src/readJson.ts +6 -0
- package/templates/pack-app/packages/node/tsconfig.json +17 -0
- package/templates/pack-app/packages/react-vite-lib/README.md +50 -0
- package/templates/pack-app/packages/react-vite-lib/__tests__/Sum.test.ts +9 -0
- package/templates/pack-app/packages/react-vite-lib/__tests__/Text.test.tsx +11 -0
- package/templates/pack-app/packages/react-vite-lib/eslint.config.js +28 -0
- package/templates/pack-app/packages/react-vite-lib/index.html +13 -0
- package/templates/pack-app/packages/react-vite-lib/package.json +30 -0
- package/templates/pack-app/packages/react-vite-lib/public/vite.svg +1 -0
- package/templates/pack-app/packages/react-vite-lib/src/calc.ts +3 -0
- package/templates/pack-app/packages/react-vite-lib/src/commponents/Text.tsx +7 -0
- package/templates/pack-app/packages/react-vite-lib/src/index.ts +2 -0
- package/templates/pack-app/packages/react-vite-lib/src/vite-env.d.ts +1 -0
- package/templates/pack-app/packages/react-vite-lib/tsconfig.json +25 -0
- package/templates/pack-app/packages/react-vite-lib/vite.config.ts +24 -0
- package/templates/pack-app/pnpm-workspace.yaml +2 -0
- package/templates/pack-app/tsconfig.json +9 -0
- package/templates/pack-app/tsconfig.test.json +10 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SliceStore } from '@qlover/slice-store';
|
|
2
|
+
|
|
3
|
+
export class FeController<T> extends SliceStore<T> {
|
|
4
|
+
constructor(initialState: T) {
|
|
5
|
+
super(() => initialState);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
getState(): T {
|
|
9
|
+
return this.state;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
setState(state: Partial<T>): void {
|
|
13
|
+
this.emit({ ...this.state, ...state });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useSliceStore } from '@qlover/slice-store-react';
|
|
2
|
+
import { FeController } from './FeController';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook for managing controller instances and their state.
|
|
7
|
+
* Change controller to UIController
|
|
8
|
+
* @param createController Factory function that creates a controller instance or controller instance
|
|
9
|
+
* @returns Controller instance with precise type inference
|
|
10
|
+
*/
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
export function useController<C extends FeController<any>>(
|
|
13
|
+
createController: (() => C) | C
|
|
14
|
+
): C {
|
|
15
|
+
const controllerRef = useRef<C | null>(null);
|
|
16
|
+
const [, forceUpdate] = useState(0);
|
|
17
|
+
|
|
18
|
+
if (!controllerRef.current) {
|
|
19
|
+
controllerRef.current =
|
|
20
|
+
typeof createController === 'function'
|
|
21
|
+
? createController()
|
|
22
|
+
: createController;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const controller = controllerRef.current!;
|
|
27
|
+
|
|
28
|
+
const unsubscribe = controller.observe(() => {
|
|
29
|
+
forceUpdate((prev) => prev + 1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
unsubscribe();
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
return controllerRef.current;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a Controller instance and cache it to avoid repeated creation
|
|
42
|
+
*
|
|
43
|
+
* @see https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useCreation/index.ts
|
|
44
|
+
* @param Controller
|
|
45
|
+
* @param args
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
export function useCreateController<
|
|
49
|
+
T,
|
|
50
|
+
Ctrl extends FeController<T>,
|
|
51
|
+
Args extends unknown[]
|
|
52
|
+
>(Controller: new (...args: Args) => Ctrl, ...args: Args): Ctrl {
|
|
53
|
+
const { current } = useRef({
|
|
54
|
+
target: undefined as undefined | Ctrl,
|
|
55
|
+
initialized: false
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!current.initialized) {
|
|
59
|
+
current.target = new Controller(...args);
|
|
60
|
+
current.initialized = true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return current.target as Ctrl;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function useControllerState<T, S = T>(
|
|
67
|
+
controller: FeController<T>,
|
|
68
|
+
selector?: (state: T) => S
|
|
69
|
+
): S {
|
|
70
|
+
return useSliceStore(controller, selector);
|
|
71
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FeController } from '@lib/fe-react-controller';
|
|
2
|
+
import { ThemeControllerProps, ThemeControllerState } from './type';
|
|
3
|
+
import { ThemeStateGetter } from './ThemeStateGetter';
|
|
4
|
+
|
|
5
|
+
export class ThemeController extends FeController<ThemeControllerState> {
|
|
6
|
+
constructor(private props: ThemeControllerProps) {
|
|
7
|
+
super(ThemeStateGetter.create(props));
|
|
8
|
+
|
|
9
|
+
this.bindToTheme();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getSupportedThemes(): string[] {
|
|
13
|
+
return this.props.supportedThemes;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
bindToTheme(): void {
|
|
17
|
+
const { theme } = this.getState();
|
|
18
|
+
|
|
19
|
+
const { domAttribute } = this.props;
|
|
20
|
+
|
|
21
|
+
if (domAttribute) {
|
|
22
|
+
document.documentElement.setAttribute(domAttribute, theme);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
changeTheme(theme: string): void {
|
|
27
|
+
if (theme === ThemeStateGetter.SYSTEM_THEME) {
|
|
28
|
+
theme = ThemeStateGetter.getSystemTheme();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.setState({ theme });
|
|
32
|
+
|
|
33
|
+
const { storage, storageKey } = this.props;
|
|
34
|
+
if (storage && storageKey) {
|
|
35
|
+
storage.setItem(storageKey, theme);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.bindToTheme();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import isString from 'lodash/isString';
|
|
2
|
+
import { ThemeControllerProps, ThemeControllerState } from './type';
|
|
3
|
+
|
|
4
|
+
export class ThemeStateGetter {
|
|
5
|
+
static SYSTEM_THEME = 'system';
|
|
6
|
+
|
|
7
|
+
static create(props: ThemeControllerProps): ThemeControllerState {
|
|
8
|
+
const theme = ThemeStateGetter.getDefaultTheme(props);
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
theme
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static getDefaultTheme(props: ThemeControllerProps): string {
|
|
16
|
+
const { storage, storageKey, defaultTheme } = props;
|
|
17
|
+
|
|
18
|
+
let theme;
|
|
19
|
+
|
|
20
|
+
// Trying to use local storage
|
|
21
|
+
if (storage && storageKey) {
|
|
22
|
+
theme = storage.getItem(storageKey);
|
|
23
|
+
|
|
24
|
+
if (isString(theme) && props.supportedThemes.includes(theme)) {
|
|
25
|
+
return theme;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (theme === ThemeStateGetter.SYSTEM_THEME) {
|
|
30
|
+
return ThemeStateGetter.getSystemTheme();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// if local storage does not have theme, use system theme
|
|
34
|
+
if (defaultTheme) {
|
|
35
|
+
if (defaultTheme === ThemeStateGetter.SYSTEM_THEME) {
|
|
36
|
+
return ThemeStateGetter.getSystemTheme();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return defaultTheme;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return ThemeStateGetter.getSystemTheme();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static getSystemTheme(): 'dark' | 'light' {
|
|
46
|
+
// use window.matchMedia to detect prefers-color-scheme media query
|
|
47
|
+
const isDarkMode =
|
|
48
|
+
window.matchMedia &&
|
|
49
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
50
|
+
|
|
51
|
+
return isDarkMode ? 'dark' : 'light';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import plugin from 'tailwindcss/plugin';
|
|
2
|
+
import template from 'lodash/template';
|
|
3
|
+
import isPlainObject from 'lodash/isPlainObject';
|
|
4
|
+
import isString from 'lodash/isString';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KeyTemplate {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
/**
|
|
10
|
+
* @type {import('./type').ThemeConfig}
|
|
11
|
+
*/
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the colors value
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```
|
|
20
|
+
* colors: {
|
|
21
|
+
* primary: {
|
|
22
|
+
* "500": getColorsValue()
|
|
23
|
+
* },
|
|
24
|
+
* secondary: getColorsValue()
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @param {string} colorKey - The color key
|
|
29
|
+
* @param {string} value - The color value
|
|
30
|
+
* @returns {string} - The colors value
|
|
31
|
+
*/
|
|
32
|
+
getColorsValue({ key, parentKey = '', value }) {
|
|
33
|
+
const { colorsValueTemplate, ...rest } = this.options;
|
|
34
|
+
|
|
35
|
+
let colorKey = key;
|
|
36
|
+
if (parentKey) {
|
|
37
|
+
colorKey = this.composeKey(key, parentKey);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// if colorsValueTemplate is provided, use it to generate the colors value
|
|
41
|
+
if (colorsValueTemplate) {
|
|
42
|
+
const styleKey = this.getStyleKey(colorKey);
|
|
43
|
+
return template(colorsValueTemplate)({
|
|
44
|
+
...rest,
|
|
45
|
+
styleKey,
|
|
46
|
+
key,
|
|
47
|
+
parentKey,
|
|
48
|
+
value
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the style theme key
|
|
57
|
+
*
|
|
58
|
+
* ```
|
|
59
|
+
* <style>
|
|
60
|
+
* [getStyleThemeKey()] {
|
|
61
|
+
* primary: #000;
|
|
62
|
+
* }
|
|
63
|
+
* </style>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @param {string} theme - The theme name
|
|
67
|
+
* @returns {string} - The style theme key
|
|
68
|
+
*/
|
|
69
|
+
getStyleThemeKey(theme) {
|
|
70
|
+
const { styleThemeKeyTemplate, target, ...rest } = this.options;
|
|
71
|
+
|
|
72
|
+
// if styleThemeKeyTemplate is provided, use it to generate the style theme key
|
|
73
|
+
if (styleThemeKeyTemplate) {
|
|
74
|
+
return template(styleThemeKeyTemplate)({ ...rest, theme, target });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return `${target}.${theme}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the style key template
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
*
|
|
85
|
+
* ```
|
|
86
|
+
* <style>
|
|
87
|
+
* [getStyleThemeKey()] {
|
|
88
|
+
* [getStyleKeyTemplate()]: #000;
|
|
89
|
+
* }
|
|
90
|
+
* </style>
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @param {string} key - The key name
|
|
94
|
+
* @param {string | undefined} parentKey - The parent key name
|
|
95
|
+
* @returns {string} - The style key template
|
|
96
|
+
*/
|
|
97
|
+
getStyleKey(key, parentKey) {
|
|
98
|
+
const { styleKeyTemplate, ...rest } = this.options;
|
|
99
|
+
|
|
100
|
+
// if parentKey is provided, compose the key
|
|
101
|
+
let colorKey = key;
|
|
102
|
+
if (parentKey) {
|
|
103
|
+
colorKey = this.composeKey(key, parentKey);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// if styleKeyTemplate is provided, use it to generate the style key
|
|
107
|
+
if (styleKeyTemplate) {
|
|
108
|
+
return template(styleKeyTemplate)({ ...rest, colorKey, parentKey });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return key;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
composeKey(key, parentKey) {
|
|
115
|
+
return parentKey ? `${parentKey}-${key}` : key;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class Generator {
|
|
120
|
+
constructor(options) {
|
|
121
|
+
this.options = options;
|
|
122
|
+
this.keyTemplate = new KeyTemplate(options);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
generateBaseStyles() {
|
|
126
|
+
const { colors } = this.options;
|
|
127
|
+
|
|
128
|
+
const baseStyles = {};
|
|
129
|
+
|
|
130
|
+
Object.entries(colors).forEach(([theme, themeColors]) => {
|
|
131
|
+
const styleThemeKey = this.keyTemplate.getStyleThemeKey(theme);
|
|
132
|
+
|
|
133
|
+
baseStyles[styleThemeKey] = {};
|
|
134
|
+
|
|
135
|
+
Object.entries(themeColors).forEach(([colorKey, value]) => {
|
|
136
|
+
if (isPlainObject(value)) {
|
|
137
|
+
Object.entries(value).forEach(([key, colorValue]) => {
|
|
138
|
+
baseStyles[styleThemeKey][
|
|
139
|
+
this.keyTemplate.getStyleKey(key, colorKey)
|
|
140
|
+
] = colorValue;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// if value is string, it means it's a color value
|
|
144
|
+
else if (isString(value)) {
|
|
145
|
+
baseStyles[styleThemeKey][this.keyTemplate.getStyleKey(colorKey)] =
|
|
146
|
+
value;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return this.setDefaultStyleThemeKey(baseStyles);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
setDefaultStyleThemeKey(baseStyles) {
|
|
155
|
+
const { defaultTheme, colors } = this.options;
|
|
156
|
+
|
|
157
|
+
// set default style theme key
|
|
158
|
+
let _defaultTheme = defaultTheme;
|
|
159
|
+
if (
|
|
160
|
+
// if default theme is not provided, set it to the first theme
|
|
161
|
+
!defaultTheme ||
|
|
162
|
+
// if default theme is system, set it to the first theme
|
|
163
|
+
defaultTheme === 'system' ||
|
|
164
|
+
// if default theme is not in colors, set it to the first theme
|
|
165
|
+
!Object.keys(colors).includes(defaultTheme)
|
|
166
|
+
) {
|
|
167
|
+
_defaultTheme = Object.keys(colors)?.[0];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const defaultStyleThemeKey =
|
|
171
|
+
this.keyTemplate.getStyleThemeKey(_defaultTheme);
|
|
172
|
+
|
|
173
|
+
const result = {};
|
|
174
|
+
Object.entries(baseStyles).forEach(([key, value]) => {
|
|
175
|
+
if (key === defaultStyleThemeKey) {
|
|
176
|
+
result[':root'] = value;
|
|
177
|
+
} else {
|
|
178
|
+
result[key] = value;
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
generateThemeColors() {
|
|
186
|
+
const { colors } = this.options;
|
|
187
|
+
|
|
188
|
+
const themeResultColors = {};
|
|
189
|
+
|
|
190
|
+
Object.entries(colors).forEach(([, themeColors]) => {
|
|
191
|
+
Object.entries(themeColors).forEach(([colorKey, value]) => {
|
|
192
|
+
themeResultColors[colorKey] = {};
|
|
193
|
+
|
|
194
|
+
if (isPlainObject(value)) {
|
|
195
|
+
Object.entries(value).forEach(([key, colorValue]) => {
|
|
196
|
+
const numberKey = Number(key);
|
|
197
|
+
themeResultColors[colorKey][numberKey] =
|
|
198
|
+
this.keyTemplate.getColorsValue({
|
|
199
|
+
key,
|
|
200
|
+
parentKey: colorKey,
|
|
201
|
+
value: colorValue
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// if value is string, it means it's a color value
|
|
206
|
+
else if (isString(value)) {
|
|
207
|
+
themeResultColors[colorKey] = this.keyTemplate.getColorsValue({
|
|
208
|
+
key: colorKey,
|
|
209
|
+
value
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return themeResultColors;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function create(options) {
|
|
220
|
+
const generator = new Generator(options);
|
|
221
|
+
|
|
222
|
+
const baseStyles = generator.generateBaseStyles();
|
|
223
|
+
const themeColors = generator.generateThemeColors();
|
|
224
|
+
|
|
225
|
+
const result = {
|
|
226
|
+
baseStyles,
|
|
227
|
+
colors: themeColors
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (Object.keys(baseStyles).length > 0) {
|
|
231
|
+
result.plugin = plugin(({ addBase }) => {
|
|
232
|
+
addBase(baseStyles);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default create;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { JSONStorage } from '@qlover/fe-utils';
|
|
2
|
+
|
|
3
|
+
export type ThemeControllerState = {
|
|
4
|
+
theme: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type ThemeConfig = {
|
|
8
|
+
domAttribute: string;
|
|
9
|
+
defaultTheme: string;
|
|
10
|
+
target: string;
|
|
11
|
+
supportedThemes: string[];
|
|
12
|
+
storageKey: string;
|
|
13
|
+
styleThemeKeyTemplate: string;
|
|
14
|
+
styleKeyTemplate: string;
|
|
15
|
+
colorsValueTemplate: string;
|
|
16
|
+
colors: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface ThemeControllerProps extends ThemeConfig {
|
|
20
|
+
storage?: JSONStorage;
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExecutorContext,
|
|
3
|
+
ExecutorPlugin,
|
|
4
|
+
RequestAdapterFetchConfig,
|
|
5
|
+
RequestAdapterResponse
|
|
6
|
+
} from '@qlover/fe-utils';
|
|
7
|
+
import { StreamProcessor } from './StreamProcessor';
|
|
8
|
+
|
|
9
|
+
export class OpenAIAuthPlugin
|
|
10
|
+
implements ExecutorPlugin<RequestAdapterFetchConfig>
|
|
11
|
+
{
|
|
12
|
+
readonly pluginName = 'OpenAIAuthPlugin';
|
|
13
|
+
|
|
14
|
+
async onSuccess(
|
|
15
|
+
context: ExecutorContext<RequestAdapterFetchConfig>
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
const adapterResponse = context.returnValue as RequestAdapterResponse<
|
|
18
|
+
Request,
|
|
19
|
+
Response
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
if (adapterResponse.data.body instanceof ReadableStream) {
|
|
23
|
+
const processer = new StreamProcessor();
|
|
24
|
+
const data = await processer.processStream(adapterResponse.data.body);
|
|
25
|
+
// overried response data
|
|
26
|
+
context.returnValue = data;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RequestScheduler,
|
|
3
|
+
RequestAdapterConfig,
|
|
4
|
+
RequestAdapterFetch,
|
|
5
|
+
RequestAdapterFetchConfig,
|
|
6
|
+
FetchURLPlugin,
|
|
7
|
+
RequestAdapterResponse
|
|
8
|
+
} from '@qlover/fe-utils';
|
|
9
|
+
import { StreamResultType } from './StreamProcessor';
|
|
10
|
+
import { OpenAIAuthPlugin } from './OpenAIAuthPlugin';
|
|
11
|
+
import {
|
|
12
|
+
RequestCommonPlugin,
|
|
13
|
+
RequestCommonPluginConfig
|
|
14
|
+
} from '@lib/request-common-plugin';
|
|
15
|
+
export interface ApiMessage {
|
|
16
|
+
content: string;
|
|
17
|
+
role: 'user' | 'system' | 'assistant';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OpenAIChatParmas {
|
|
21
|
+
model?: string;
|
|
22
|
+
messages: ApiMessage[];
|
|
23
|
+
stream?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type OpenAIAargs = {
|
|
27
|
+
commonPluginConfig: RequestCommonPluginConfig;
|
|
28
|
+
models: readonly string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type OpenAIClientConfig = Partial<RequestAdapterFetchConfig> &
|
|
32
|
+
OpenAIAargs;
|
|
33
|
+
|
|
34
|
+
export class OpenAIClient extends RequestScheduler<RequestAdapterConfig> {
|
|
35
|
+
constructor(config: OpenAIClientConfig) {
|
|
36
|
+
const { commonPluginConfig, ...rest } = config;
|
|
37
|
+
super(new RequestAdapterFetch(rest));
|
|
38
|
+
|
|
39
|
+
this.usePlugin(new FetchURLPlugin());
|
|
40
|
+
this.usePlugin(new RequestCommonPlugin(commonPluginConfig));
|
|
41
|
+
this.usePlugin(new OpenAIAuthPlugin());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async completion(
|
|
45
|
+
params: OpenAIChatParmas
|
|
46
|
+
): Promise<RequestAdapterResponse<StreamResultType>> {
|
|
47
|
+
return this.post('/chat/completions', {
|
|
48
|
+
data: params
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type StreamResultType = {
|
|
2
|
+
content: string;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export class StreamProcessor {
|
|
6
|
+
private decoder = new TextDecoder('utf-8');
|
|
7
|
+
private buffer = '';
|
|
8
|
+
|
|
9
|
+
async processStream(
|
|
10
|
+
readableStream: ReadableStream<Uint8Array>
|
|
11
|
+
): Promise<StreamResultType> {
|
|
12
|
+
let fullContent = '';
|
|
13
|
+
|
|
14
|
+
const reader = readableStream.getReader();
|
|
15
|
+
|
|
16
|
+
const shouldStop = (result: ReadableStreamReadResult<any>): boolean => {
|
|
17
|
+
return result.done;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
while (true) {
|
|
22
|
+
const readResult = await reader.read();
|
|
23
|
+
if (shouldStop(readResult)) break;
|
|
24
|
+
|
|
25
|
+
const decodedChunk = this.decoder.decode(readResult.value, {
|
|
26
|
+
stream: true
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const processLineResult = await this.processChunk(decodedChunk);
|
|
30
|
+
|
|
31
|
+
fullContent += processLineResult.value;
|
|
32
|
+
|
|
33
|
+
if (processLineResult.done) break;
|
|
34
|
+
}
|
|
35
|
+
} finally {
|
|
36
|
+
reader.releaseLock();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: fullContent
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
processLine(line: string) {
|
|
45
|
+
const data = line.startsWith('data:') ? line.slice(6).trim() : '';
|
|
46
|
+
|
|
47
|
+
if (data === '[DONE]') {
|
|
48
|
+
return { done: true, value: '' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const value = data ? JSON.parse(data).choices[0]?.delta?.content : '';
|
|
52
|
+
|
|
53
|
+
return { done: false, value: value };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async processChunk(chunk: string) {
|
|
57
|
+
let processedContent = '';
|
|
58
|
+
this.buffer += chunk;
|
|
59
|
+
const lines = this.buffer.split('\n');
|
|
60
|
+
this.buffer = lines.pop() || '';
|
|
61
|
+
for (const line of lines) {
|
|
62
|
+
try {
|
|
63
|
+
const lineResult = this.processLine(line);
|
|
64
|
+
|
|
65
|
+
if (lineResult.done) {
|
|
66
|
+
return { value: processedContent, done: true };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!lineResult.value) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
processedContent += lineResult.value;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Parsing error:', error);
|
|
76
|
+
this.buffer = line + '\n' + this.buffer;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { value: processedContent, done: false };
|
|
80
|
+
}
|
|
81
|
+
}
|