@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.
Files changed (144) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/bin/create-app.js +28 -0
  3. package/dist/cjs/index.d.ts +67 -0
  4. package/dist/cjs/index.js +1 -0
  5. package/dist/es/index.d.ts +67 -0
  6. package/dist/es/index.js +1 -0
  7. package/package.json +60 -0
  8. package/templates/fe-react/.env +3 -0
  9. package/templates/fe-react/README.md +50 -0
  10. package/templates/fe-react/config/app.common.ts +52 -0
  11. package/templates/fe-react/config/app.router.json +150 -0
  12. package/templates/fe-react/config/feapi.mock.json +14 -0
  13. package/templates/fe-react/config/i18n.ts +21 -0
  14. package/templates/fe-react/config/theme.json +90 -0
  15. package/templates/fe-react/eslint.config.js +31 -0
  16. package/templates/fe-react/index.html +13 -0
  17. package/templates/fe-react/lib/fe-react-controller/FeController.ts +15 -0
  18. package/templates/fe-react/lib/fe-react-controller/index.ts +2 -0
  19. package/templates/fe-react/lib/fe-react-controller/useController.ts +71 -0
  20. package/templates/fe-react/lib/fe-react-theme/ThemeController.ts +40 -0
  21. package/templates/fe-react/lib/fe-react-theme/ThemeStateGetter.ts +53 -0
  22. package/templates/fe-react/lib/fe-react-theme/index.ts +3 -0
  23. package/templates/fe-react/lib/fe-react-theme/tw-generator.js +239 -0
  24. package/templates/fe-react/lib/fe-react-theme/type.ts +21 -0
  25. package/templates/fe-react/lib/openAiApi/OpenAIAuthPlugin.ts +29 -0
  26. package/templates/fe-react/lib/openAiApi/OpenAIClient.ts +51 -0
  27. package/templates/fe-react/lib/openAiApi/StreamProcessor.ts +81 -0
  28. package/templates/fe-react/lib/openAiApi/index.ts +3 -0
  29. package/templates/fe-react/lib/request-common-plugin/index.ts +169 -0
  30. package/templates/fe-react/lib/tw-root10px/index.css +4 -0
  31. package/templates/fe-react/lib/tw-root10px/index.js +178 -0
  32. package/templates/fe-react/package.json +49 -0
  33. package/templates/fe-react/postcss.config.js +6 -0
  34. package/templates/fe-react/public/locales/en/about.json +3 -0
  35. package/templates/fe-react/public/locales/en/common.json +6 -0
  36. package/templates/fe-react/public/locales/en/executor.json +6 -0
  37. package/templates/fe-react/public/locales/en/home.json +10 -0
  38. package/templates/fe-react/public/locales/en/jsonStorage.json +11 -0
  39. package/templates/fe-react/public/locales/en/login.json +7 -0
  40. package/templates/fe-react/public/locales/en/request.json +15 -0
  41. package/templates/fe-react/public/locales/zh/about.json +3 -0
  42. package/templates/fe-react/public/locales/zh/common.json +7 -0
  43. package/templates/fe-react/public/locales/zh/executor.json +7 -0
  44. package/templates/fe-react/public/locales/zh/home.json +10 -0
  45. package/templates/fe-react/public/locales/zh/jsonStorage.json +11 -0
  46. package/templates/fe-react/public/locales/zh/login.json +8 -0
  47. package/templates/fe-react/public/locales/zh/request.json +15 -0
  48. package/templates/fe-react/public/logo.svg +1 -0
  49. package/templates/fe-react/src/App.tsx +20 -0
  50. package/templates/fe-react/src/assets/react.svg +1 -0
  51. package/templates/fe-react/src/components/Loading.tsx +41 -0
  52. package/templates/fe-react/src/components/LocaleLink.tsx +42 -0
  53. package/templates/fe-react/src/components/ProcessProvider.tsx +41 -0
  54. package/templates/fe-react/src/components/ThemeSwitcher.tsx +29 -0
  55. package/templates/fe-react/src/containers/context/BaseRouteContext.ts +27 -0
  56. package/templates/fe-react/src/containers/context/BaseRouteProvider.tsx +11 -0
  57. package/templates/fe-react/src/containers/globals.ts +31 -0
  58. package/templates/fe-react/src/containers/index.ts +71 -0
  59. package/templates/fe-react/src/hooks/useLanguageGuard.ts +25 -0
  60. package/templates/fe-react/src/hooks/useStrictEffect.ts +29 -0
  61. package/templates/fe-react/src/main.tsx +15 -0
  62. package/templates/fe-react/src/pages/404.tsx +14 -0
  63. package/templates/fe-react/src/pages/500.tsx +14 -0
  64. package/templates/fe-react/src/pages/auth/Layout.tsx +14 -0
  65. package/templates/fe-react/src/pages/auth/Login.tsx +62 -0
  66. package/templates/fe-react/src/pages/auth/Register.tsx +3 -0
  67. package/templates/fe-react/src/pages/base/About.tsx +12 -0
  68. package/templates/fe-react/src/pages/base/Executor.tsx +38 -0
  69. package/templates/fe-react/src/pages/base/Home.tsx +78 -0
  70. package/templates/fe-react/src/pages/base/JSONStorage.tsx +124 -0
  71. package/templates/fe-react/src/pages/base/Layout.tsx +17 -0
  72. package/templates/fe-react/src/pages/base/RedirectPathname.tsx +15 -0
  73. package/templates/fe-react/src/pages/base/Request.tsx +91 -0
  74. package/templates/fe-react/src/pages/base/components/BaseHeader.tsx +19 -0
  75. package/templates/fe-react/src/pages/index.tsx +108 -0
  76. package/templates/fe-react/src/services/controllers/ExecutorController.ts +56 -0
  77. package/templates/fe-react/src/services/controllers/JSONStorageController.ts +42 -0
  78. package/templates/fe-react/src/services/controllers/RequestController.ts +105 -0
  79. package/templates/fe-react/src/services/controllers/RouterController.ts +90 -0
  80. package/templates/fe-react/src/services/controllers/UserController.ts +146 -0
  81. package/templates/fe-react/src/services/feApi/FeApi.ts +51 -0
  82. package/templates/fe-react/src/services/feApi/FeApiMockPlugin.ts +42 -0
  83. package/templates/fe-react/src/services/feApi/FeApiType.ts +55 -0
  84. package/templates/fe-react/src/services/feApi/index.ts +2 -0
  85. package/templates/fe-react/src/services/i18n/index.ts +50 -0
  86. package/templates/fe-react/src/services/pageProcesser/PageProcesser.ts +29 -0
  87. package/templates/fe-react/src/services/pageProcesser/index.ts +1 -0
  88. package/templates/fe-react/src/styles/css/index.css +2 -0
  89. package/templates/fe-react/src/styles/css/page.css +3 -0
  90. package/templates/fe-react/src/styles/css/tailwind.css +3 -0
  91. package/templates/fe-react/src/types/Page.ts +49 -0
  92. package/templates/fe-react/src/types/UIDependenciesInterface.ts +31 -0
  93. package/templates/fe-react/src/types/global.d.ts +7 -0
  94. package/templates/fe-react/src/utils/RequestLogger.ts +34 -0
  95. package/templates/fe-react/src/utils/datetime.ts +25 -0
  96. package/templates/fe-react/src/utils/thread.ts +3 -0
  97. package/templates/fe-react/src/vite-env.d.ts +1 -0
  98. package/templates/fe-react/tailwind.config.js +18 -0
  99. package/templates/fe-react/tsconfig.app.json +29 -0
  100. package/templates/fe-react/tsconfig.json +7 -0
  101. package/templates/fe-react/tsconfig.node.json +22 -0
  102. package/templates/fe-react/vite.config.ts +32 -0
  103. package/templates/pack-app/.editorconfig +23 -0
  104. package/templates/pack-app/.env +5 -0
  105. package/templates/pack-app/.env.template +6 -0
  106. package/templates/pack-app/.gitattributes +2 -0
  107. package/templates/pack-app/.github/workflows/general-check.yml +50 -0
  108. package/templates/pack-app/.github/workflows/release.yml.template +110 -0
  109. package/templates/pack-app/.prettierignore +5 -0
  110. package/templates/pack-app/.prettierrc.js +3 -0
  111. package/templates/pack-app/CHANGELOG.md +0 -0
  112. package/templates/pack-app/README.md +1 -0
  113. package/templates/pack-app/eslint.config.js +77 -0
  114. package/templates/pack-app/fe-config.json +10 -0
  115. package/templates/pack-app/jest.config.js +31 -0
  116. package/templates/pack-app/package.json +66 -0
  117. package/templates/pack-app/packages/browser/__tests__/calc.test.ts +9 -0
  118. package/templates/pack-app/packages/browser/package.json +11 -0
  119. package/templates/pack-app/packages/browser/rollup.config.js +70 -0
  120. package/templates/pack-app/packages/browser/src/calc.ts +3 -0
  121. package/templates/pack-app/packages/browser/src/index.ts +1 -0
  122. package/templates/pack-app/packages/browser/tsconfig.json +15 -0
  123. package/templates/pack-app/packages/node/__tests__/readJson.test.ts +25 -0
  124. package/templates/pack-app/packages/node/package.json +11 -0
  125. package/templates/pack-app/packages/node/rollup.config.js +89 -0
  126. package/templates/pack-app/packages/node/src/index.ts +7 -0
  127. package/templates/pack-app/packages/node/src/readJson.ts +6 -0
  128. package/templates/pack-app/packages/node/tsconfig.json +17 -0
  129. package/templates/pack-app/packages/react-vite-lib/README.md +50 -0
  130. package/templates/pack-app/packages/react-vite-lib/__tests__/Sum.test.ts +9 -0
  131. package/templates/pack-app/packages/react-vite-lib/__tests__/Text.test.tsx +11 -0
  132. package/templates/pack-app/packages/react-vite-lib/eslint.config.js +28 -0
  133. package/templates/pack-app/packages/react-vite-lib/index.html +13 -0
  134. package/templates/pack-app/packages/react-vite-lib/package.json +30 -0
  135. package/templates/pack-app/packages/react-vite-lib/public/vite.svg +1 -0
  136. package/templates/pack-app/packages/react-vite-lib/src/calc.ts +3 -0
  137. package/templates/pack-app/packages/react-vite-lib/src/commponents/Text.tsx +7 -0
  138. package/templates/pack-app/packages/react-vite-lib/src/index.ts +2 -0
  139. package/templates/pack-app/packages/react-vite-lib/src/vite-env.d.ts +1 -0
  140. package/templates/pack-app/packages/react-vite-lib/tsconfig.json +25 -0
  141. package/templates/pack-app/packages/react-vite-lib/vite.config.ts +24 -0
  142. package/templates/pack-app/pnpm-workspace.yaml +2 -0
  143. package/templates/pack-app/tsconfig.json +9 -0
  144. 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,2 @@
1
+ export * from './FeController';
2
+ export * from './useController';
@@ -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,3 @@
1
+ export * from './ThemeController';
2
+ export * from './ThemeStateGetter';
3
+ export * from './type';
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './OpenAIClient';
2
+ export * from './OpenAIAuthPlugin';
3
+ export * from './StreamProcessor';