├── .editorconfig
├── .env
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── config
├── config.ts
├── defaultSettings.ts
├── oneapi.json
├── proxy.ts
└── routes.ts
├── jest.config.ts
├── jsconfig.json
├── package.json
├── pnpm-lock.yaml
├── public
├── CNAME
├── favicon.ico
├── insightface.webp
├── logo.png
├── logo.svg
├── midjourney.webp
├── niji.webp
└── scripts
│ └── loading.js
├── src
├── access.ts
├── app.tsx
├── components
│ ├── HeaderDropdown
│ │ └── index.tsx
│ ├── JsonEditor
│ │ └── index.tsx
│ └── RightContent
│ │ ├── AvatarDropdown.tsx
│ │ └── index.tsx
├── global.less
├── global.tsx
├── locales
│ ├── en-US.ts
│ ├── en-US
│ │ ├── component.ts
│ │ ├── globalHeader.ts
│ │ ├── menu.ts
│ │ ├── pages.ts
│ │ ├── pwa.ts
│ │ ├── settingDrawer.ts
│ │ └── settings.ts
│ ├── zh-CN.ts
│ └── zh-CN
│ │ ├── component.ts
│ │ ├── globalHeader.ts
│ │ ├── menu.ts
│ │ ├── pages.ts
│ │ ├── pwa.ts
│ │ ├── settingDrawer.ts
│ │ └── settings.ts
├── manifest.json
├── pages
│ ├── 404.tsx
│ ├── AccountList
│ │ ├── components
│ │ │ ├── button
│ │ │ │ ├── DelButton.tsx
│ │ │ │ └── SyncButton.tsx
│ │ │ └── contents
│ │ │ │ ├── AddContent.tsx
│ │ │ │ ├── CfContent.tsx
│ │ │ │ ├── MoreContent.tsx
│ │ │ │ ├── ReconnectContent.tsx
│ │ │ │ └── UpdateContent.tsx
│ │ ├── index.less
│ │ └── index.tsx
│ ├── BannedWordList
│ │ ├── List
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ └── components
│ │ │ └── AddContent.tsx
│ ├── DomainList
│ │ ├── List
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ └── components
│ │ │ └── AddContent.tsx
│ ├── Draw
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Probe
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Setting
│ │ ├── index.less
│ │ └── index.tsx
│ ├── Task
│ │ ├── List
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ └── components
│ │ │ └── TaskContent.tsx
│ ├── User
│ │ └── Login
│ │ │ └── index.tsx
│ ├── UserList
│ │ ├── List
│ │ │ ├── index.less
│ │ │ └── index.tsx
│ │ └── components
│ │ │ └── AddContent.tsx
│ ├── Welcome.tsx
│ └── components
│ │ └── Modal.tsx
├── requestErrorConfig.ts
├── service-worker.js
├── services
│ ├── mj
│ │ ├── api.ts
│ │ ├── index.ts
│ │ └── typings.d.ts
│ └── swagger
│ │ ├── index.ts
│ │ ├── pet.ts
│ │ ├── store.ts
│ │ ├── typings.d.ts
│ │ └── user.ts
└── typings.d.ts
├── tests
└── setupTests.jsx
├── tsconfig.json
└── types
└── cache
├── cache.json
└── mock
└── mock.cache.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # MJ-SERVER
2 | MJ_SERVER=http://127.0.0.1:8080
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /lambda/
2 | /scripts
3 | /config
4 | .history
5 | public
6 | dist
7 | .umi
8 | mock
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [require.resolve('@umijs/lint/dist/config/eslint')],
3 | globals: {
4 | page: true,
5 | REACT_APP_ENV: true,
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | **/node_modules
5 | # roadhog-api-doc ignore
6 | /src/utils/request-temp.js
7 | _roadhog-api-doc
8 |
9 | # production
10 | /dist
11 |
12 | # misc
13 | .DS_Store
14 | npm-debug.log*
15 | yarn-error.log
16 |
17 | /coverage
18 | .idea
19 | yarn.lock
20 | package-lock.json
21 | *bak
22 | .vscode
23 |
24 |
25 | # visual studio code
26 | .history
27 | *.log
28 | functions/*
29 | .temp/**
30 |
31 | # umi
32 | .umi
33 | .umi-production
34 | .umi-test
35 |
36 | # screenshot
37 | screenshot
38 | .firebase
39 | .eslintcache
40 |
41 | build
42 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.svg
2 | .umi
3 | .umi-production
4 | /dist
5 | .dockerignore
6 | .DS_Store
7 | .eslintignore
8 | *.png
9 | *.toml
10 | docker
11 | .editorconfig
12 | Dockerfile*
13 | .gitignore
14 | .prettierignore
15 | LICENSE
16 | .eslintcache
17 | *.lock
18 | yarn-error.log
19 | .history
20 | CNAME
21 | /build
22 | /public
23 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'all',
4 | printWidth: 100,
5 | proseWrap: 'never',
6 | endOfLine: 'lf',
7 | overrides: [
8 | {
9 | files: '.prettierrc',
10 | options: {
11 | parser: 'json',
12 | },
13 | },
14 | {
15 | files: 'document.ejs',
16 | options: {
17 | parser: 'html',
18 | },
19 | },
20 | ],
21 | };
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2024 True AI Organization
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # midjourney-proxy-admin
2 |
3 | [midjourney-proxy](https://github.com/trueai-org/midjourney-proxy) 的管理后台
4 |
5 | # 主要功能
6 |
7 | - [x] 支持MJ账号的增删改查功能
8 | - [x] 支持MJ账号的详细信息查询和账号同步操作
9 | - [x] 支持MJ账号的并发队列设置
10 | - [x] 支持MJ的账号settings设置
11 | - [x] 支持MJ的任务查询
12 | - [x] 提供功能齐全的绘图测试页面
13 |
14 | # 部署方式
15 |
16 | ## 1.运行环境
17 |
18 | 支持 Linux、MacOS、Windows 系统(可在Linux服务器上长期运行),同时需安装 `node18`。
19 |
20 | **(1) 克隆项目代码:**
21 |
22 | ```bash
23 | git clone https://github.com/trueai-org/midjourney-proxy-webui
24 | cd midjourney-proxy-webui/
25 | ```
26 |
27 | **(2) 安装依赖 :**
28 |
29 | ```bash
30 | yarn
31 | ```
32 |
33 | ## 2.配置
34 |
35 | 配置文件在根目录的`.env`中:
36 |
37 | ```shell
38 | # MJ-SERVER
39 | MJ_SERVER=http://127.0.0.1:8080
40 | ```
41 |
42 | ## 3.运行
43 |
44 | ```
45 | yarn dev
46 | ```
47 |
48 |
--------------------------------------------------------------------------------
/config/config.ts:
--------------------------------------------------------------------------------
1 | // https://umijs.org/config/
2 | import { defineConfig } from '@umijs/max';
3 | import defaultSettings from './defaultSettings';
4 | import proxy from './proxy';
5 | import routes from './routes';
6 |
7 | const { REACT_APP_ENV = 'dev' } = process.env;
8 |
9 | export default defineConfig({
10 | /**
11 | * @name 开启 base 前缀配置
12 | * @description base 配置允许你为应用程序设置路由前缀
13 | * @doc https://umijs.org/docs/api/config#base
14 | */
15 | base: "",
16 | /**
17 | * @name publicPath 静态资源配置
18 | * @description publicPath 配置构建后的静态资源存放位置
19 | * @doc https://umijs.org/docs/api/config#publicPath
20 | */
21 | publicPath: '/',
22 | esbuildMinifyIIFE: true,
23 | /**
24 | * @name 开启 hash 模式
25 | * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。
26 | * @doc https://umijs.org/docs/api/config#hash
27 | */
28 | hash: true,
29 | history: { type: 'hash' },
30 |
31 | /**
32 | * @name 兼容性设置
33 | * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖
34 | * @doc https://umijs.org/docs/api/config#targets
35 | */
36 | // targets: {
37 | // ie: 11,
38 | // },
39 | /**
40 | * @name 路由的配置,不在路由中引入的文件不会编译
41 | * @description 只支持 path,component,routes,redirect,wrappers,title 的配置
42 | * @doc https://umijs.org/docs/guides/routes
43 | */
44 | // umi routes: https://umijs.org/docs/routing
45 | routes,
46 | /**
47 | * @name 主题的配置
48 | * @description 虽然叫主题,但是其实只是 less 的变量设置
49 | * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn
50 | * @doc umi 的theme 配置 https://umijs.org/docs/api/config#theme
51 | */
52 | theme: {
53 | // 如果不想要 configProvide 动态设置主题需要把这个设置为 default
54 | // 只有设置为 variable, 才能使用 configProvide 动态设置主色调
55 | 'root-entry-name': 'variable',
56 | },
57 | /**
58 | * @name moment 的国际化配置
59 | * @description 如果对国际化没有要求,打开之后能减少js的包大小
60 | * @doc https://umijs.org/docs/api/config#ignoremomentlocale
61 | */
62 | ignoreMomentLocale: true,
63 | /**
64 | * @name 代理配置
65 | * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了
66 | * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。
67 | * @doc 代理介绍 https://umijs.org/docs/guides/proxy
68 | * @doc 代理配置 https://umijs.org/docs/api/config#proxy
69 | */
70 | proxy: proxy[REACT_APP_ENV as keyof typeof proxy],
71 | /**
72 | * @name 快速热更新配置
73 | * @description 一个不错的热更新组件,更新时可以保留 state
74 | */
75 | fastRefresh: true,
76 | //============== 以下都是max的插件配置 ===============
77 | /**
78 | * @name 数据流插件
79 | * @@doc https://umijs.org/docs/max/data-flow
80 | */
81 | model: {},
82 | /**
83 | * 一个全局的初始数据流,可以用它在插件之间共享数据
84 | * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。
85 | * @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81
86 | */
87 | initialState: {},
88 | /**
89 | * @name layout 插件
90 | * @doc https://umijs.org/docs/max/layout-menu
91 | */
92 | title: 'Midjourney Proxy Admin',
93 | layout: {
94 | locale: true,
95 | ...defaultSettings,
96 | },
97 | /**
98 | * @name moment2dayjs 插件
99 | * @description 将项目中的 moment 替换为 dayjs
100 | * @doc https://umijs.org/docs/max/moment2dayjs
101 | */
102 | moment2dayjs: {
103 | preset: 'antd',
104 | plugins: ['duration'],
105 | },
106 | /**
107 | * @name 国际化插件
108 | * @doc https://umijs.org/docs/max/i18n
109 | */
110 | locale: {
111 | // default zh-CN
112 | default: 'zh-CN',
113 | antd: true,
114 | // default true, when it is true, will use `navigator.language` overwrite default
115 | baseNavigator: true,
116 | },
117 | /**
118 | * @name antd 插件
119 | * @description 内置了 babel import 插件
120 | * @doc https://umijs.org/docs/max/antd#antd
121 | */
122 | antd: {},
123 | /**
124 | * @name 网络请求配置
125 | * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
126 | * @doc https://umijs.org/docs/max/request
127 | */
128 | request: {},
129 | /**
130 | * @name 权限插件
131 | * @description 基于 initialState 的权限插件,必须先打开 initialState
132 | * @doc https://umijs.org/docs/max/access
133 | */
134 | access: {},
135 | /**
136 | * @name
中额外的 script
137 | * @description 配置 中额外的 script
138 | */
139 | headScripts: [
140 | // 解决首次加载时白屏的问题
141 | { src: '/scripts/loading.js', async: true },
142 | ],
143 | //================ pro 插件配置 =================
144 | presets: ['umi-presets-pro'],
145 | /**
146 | * @name openAPI 插件的配置
147 | * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码
148 | * @doc https://pro.ant.design/zh-cn/docs/openapi/
149 | */
150 | mfsu: {
151 | strategy: 'normal',
152 | },
153 | requestRecord: {},
154 | });
155 |
--------------------------------------------------------------------------------
/config/defaultSettings.ts:
--------------------------------------------------------------------------------
1 | import { ProLayoutProps } from '@ant-design/pro-components';
2 |
3 | /**
4 | * @name
5 | */
6 | const Settings: ProLayoutProps & {
7 | pwa?: boolean;
8 | logo?: string;
9 | } = {
10 | navTheme: 'light',
11 | // 拂晓蓝
12 | colorPrimary: '#1890ff',
13 | layout: 'mix',
14 | contentWidth: 'Fluid',
15 | fixedHeader: false,
16 | fixSiderbar: true,
17 | colorWeak: false,
18 | title: 'Midjourney Proxy Admin',
19 | pwa: true,
20 | logo: './logo.svg',
21 | iconfontUrl: '',
22 | token: {
23 | // 参见ts声明,demo 见文档,通过token 修改样式
24 | //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F
25 | },
26 | };
27 |
28 | export default Settings;
29 |
--------------------------------------------------------------------------------
/config/oneapi.json:
--------------------------------------------------------------------------------
1 | {
2 | "openapi": "3.0.1",
3 | "info": {
4 | "title": "Ant Design Pro",
5 | "version": "1.0.0"
6 | },
7 | "servers": [
8 | {
9 | "url": "http://localhost:8000/"
10 | },
11 | {
12 | "url": "https://localhost:8000/"
13 | }
14 | ],
15 | "paths": {
16 | "/swagger": {
17 | "x-swagger-pipe": "swagger_raw"
18 | }
19 | },
20 | "components": {
21 | "schemas": {
22 | "LoginResult": {
23 | "type": "object",
24 | "properties": {
25 | "status": {
26 | "type": "string"
27 | },
28 | "type": {
29 | "type": "string"
30 | },
31 | "currentAuthority": {
32 | "type": "string"
33 | }
34 | }
35 | },
36 | "PageParams": {
37 | "type": "object",
38 | "properties": {
39 | "current": {
40 | "type": "number"
41 | },
42 | "pageSize": {
43 | "type": "number"
44 | }
45 | }
46 | },
47 | "NoticeIconItem": {
48 | "type": "object",
49 | "properties": {
50 | "id": {
51 | "type": "string"
52 | },
53 | "extra": {
54 | "type": "string",
55 | "format": "any"
56 | },
57 | "key": { "type": "string" },
58 | "read": {
59 | "type": "boolean"
60 | },
61 | "avatar": {
62 | "type": "string"
63 | },
64 | "title": {
65 | "type": "string"
66 | },
67 | "status": {
68 | "type": "string"
69 | },
70 | "datetime": {
71 | "type": "string",
72 | "format": "date"
73 | },
74 | "description": {
75 | "type": "string"
76 | },
77 | "type": {
78 | "extensions": {
79 | "x-is-enum": true
80 | },
81 | "$ref": "#/components/schemas/NoticeIconItemType"
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/config/proxy.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @name 代理的配置
3 | * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
4 | * -------------------------------
5 | * The agent cannot take effect in the production environment
6 | * so there is no configuration of the production environment
7 | * For details, please see
8 | * https://pro.ant.design/docs/deploy
9 | *
10 | * @doc https://umijs.org/docs/guides/proxy
11 | */
12 |
13 | const MJ_SERVER = process.env.MJ_SERVER;
14 |
15 | export default {
16 | // 如果需要自定义本地开发服务器 请取消注释按需调整
17 | dev: {
18 | // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
19 | '/mj/': {
20 | // 要代理的地址
21 | target: MJ_SERVER,
22 | // 配置了这个可以从 http 代理到 https
23 | // 依赖 origin 的功能可能需要这个,比如 cookie
24 | changeOrigin: true,
25 | },
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/config/routes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @name umi 的路由配置
3 | * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置
4 | * @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。
5 | * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。
6 | * @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。
7 | * @param redirect 配置路由跳转
8 | * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验
9 | * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题
10 | * @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 则取值应为 user 或者 User
11 | * @doc https://umijs.org/docs/guides/routes
12 | */
13 | export default [
14 | {
15 | path: '/user',
16 | layout: false,
17 | routes: [
18 | {
19 | name: 'login',
20 | path: 'login',
21 | component: './User/Login',
22 | },
23 | ],
24 | },
25 | {
26 | path: '/welcome',
27 | name: 'welcome',
28 | icon: 'smile',
29 | component: './Welcome',
30 | },
31 | {
32 | path: '/',
33 | redirect: '/welcome',
34 | },
35 | {
36 | name: 'list.account-list',
37 | icon: 'crown',
38 | path: '/account',
39 | component: './AccountList',
40 | },
41 | {
42 | name: 'list.domain-list',
43 | icon: 'tag',
44 | path: '/domain',
45 | component: './DomainList/List',
46 | },
47 | {
48 | name: 'list.user-list',
49 | icon: 'user',
50 | path: '/user-list',
51 | component: './UserList/List',
52 | },
53 | {
54 | name: 'task-list',
55 | icon: 'bars',
56 | path: '/task',
57 | component: './Task/List',
58 | },
59 | {
60 | name: 'draw-test',
61 | icon: 'experiment',
62 | path: '/draw-test',
63 | component: './Draw',
64 | },
65 | {
66 | name: 'list.banned-word-list',
67 | icon: 'stop',
68 | path: '/banned-word',
69 | component: './BannedWordList/List',
70 | },
71 | {
72 | name: 'setting',
73 | icon: 'setting',
74 | path: '/setting',
75 | component: './Setting',
76 | },
77 | {
78 | name: 'probe',
79 | icon: 'profile',
80 | path: '/probe',
81 | component: './Probe',
82 | },
83 | {
84 | path: '*',
85 | layout: false,
86 | component: './404',
87 | },
88 | ];
89 |
--------------------------------------------------------------------------------
/jest.config.ts:
--------------------------------------------------------------------------------
1 | import { configUmiAlias, createConfig } from '@umijs/max/test';
2 |
3 | export default async () => {
4 | const config = await configUmiAlias({
5 | ...createConfig({
6 | target: 'browser',
7 | }),
8 | });
9 |
10 | console.log();
11 | return {
12 | ...config,
13 | testEnvironmentOptions: {
14 | ...(config?.testEnvironmentOptions || {}),
15 | url: 'http://localhost:8000',
16 | },
17 | setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'],
18 | globals: {
19 | ...config.globals,
20 | localStorage: null,
21 | },
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "emitDecoratorMetadata": true,
5 | "experimentalDecorators": true,
6 | "baseUrl": ".",
7 | "paths": {
8 | "@/*": ["./src/*"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "midjourney-proxy-admin",
3 | "version": "1.0",
4 | "private": true,
5 | "description": "An out-of-box UI solution for enterprise applications",
6 | "scripts": {
7 | "analyze": "cross-env ANALYZE=1 max build",
8 | "build": "max build",
9 | "deploy": "npm run build && npm run gh-pages",
10 | "dev": "npm run start:dev",
11 | "gh-pages": "gh-pages -d dist",
12 | "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
13 | "postinstall": "max setup",
14 | "jest": "jest",
15 | "lint": "npm run lint:js && npm run lint:prettier && npm run tsc",
16 | "lint-staged": "lint-staged",
17 | "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
18 | "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src ",
19 | "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
20 | "lint:prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto",
21 | "openapi": "max openapi",
22 | "prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"",
23 | "preview": "npm run build && max preview --port 8000",
24 | "record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login",
25 | "serve": "umi-serve",
26 | "start": "cross-env UMI_ENV=dev max dev",
27 | "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev",
28 | "start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev",
29 | "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
30 | "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
31 | "test": "jest",
32 | "test:coverage": "npm run jest -- --coverage",
33 | "test:update": "npm run jest -- -u",
34 | "tsc": "tsc --noEmit"
35 | },
36 | "lint-staged": {
37 | "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
38 | "**/*.{js,jsx,tsx,ts,less,md,json}": [
39 | "prettier --write"
40 | ]
41 | },
42 | "browserslist": [
43 | "> 1%",
44 | "last 2 versions",
45 | "not ie <= 10"
46 | ],
47 | "dependencies": {
48 | "@ant-design/icons": "^4.8.0",
49 | "@ant-design/pro-components": "^2.3.57",
50 | "@ant-design/use-emotion-css": "1.0.4",
51 | "@umijs/route-utils": "^2.2.2",
52 | "ace-builds": "^1.35.4",
53 | "antd": "^5.20.0",
54 | "classnames": "^2.3.2",
55 | "lodash": "^4.17.21",
56 | "moment": "^2.29.4",
57 | "omit.js": "^2.0.2",
58 | "rc-menu": "^9.8.2",
59 | "rc-util": "^5.27.2",
60 | "react": "^18.2.0",
61 | "react-ace": "^12.0.0",
62 | "react-dev-inspector": "^1.8.4",
63 | "react-dom": "^18.2.0",
64 | "react-helmet-async": "^1.3.0",
65 | "react-markdown": "^9.0.0"
66 | },
67 | "devDependencies": {
68 | "@ant-design/pro-cli": "^2.1.5",
69 | "@testing-library/react": "^13.4.0",
70 | "@types/classnames": "^2.3.1",
71 | "@types/express": "^4.17.17",
72 | "@types/history": "^4.7.11",
73 | "@types/jest": "^29.4.0",
74 | "@types/lodash": "^4.14.191",
75 | "@types/react": "^18.0.28",
76 | "@types/react-dom": "^18.0.11",
77 | "@types/react-helmet": "^6.1.6",
78 | "@umijs/fabric": "^2.14.1",
79 | "@umijs/lint": "^4.0.52",
80 | "@umijs/max": "^4.0.52",
81 | "cross-env": "^7.0.3",
82 | "eslint": "^8.34.0",
83 | "express": "^4.18.2",
84 | "gh-pages": "^3.2.3",
85 | "husky": "^7.0.4",
86 | "jest": "^29.4.3",
87 | "jest-environment-jsdom": "^29.4.3",
88 | "lint-staged": "^10.5.4",
89 | "mockjs": "^1.1.0",
90 | "prettier": "^2.8.4",
91 | "swagger-ui-dist": "^4.15.5",
92 | "ts-node": "^10.9.1",
93 | "typescript": "^4.9.5",
94 | "umi-presets-pro": "^2.0.2"
95 | },
96 | "engines": {
97 | "node": ">=12.0.0"
98 | },
99 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
100 | }
101 |
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | preview.pro.ant.design
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trueai-org/midjourney-proxy-webui/1ceaa67750eb8e4db1e4cb38a3b8f0aca6e53cad/public/favicon.ico
--------------------------------------------------------------------------------
/public/insightface.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trueai-org/midjourney-proxy-webui/1ceaa67750eb8e4db1e4cb38a3b8f0aca6e53cad/public/insightface.webp
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trueai-org/midjourney-proxy-webui/1ceaa67750eb8e4db1e4cb38a3b8f0aca6e53cad/public/logo.png
--------------------------------------------------------------------------------
/public/midjourney.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trueai-org/midjourney-proxy-webui/1ceaa67750eb8e4db1e4cb38a3b8f0aca6e53cad/public/midjourney.webp
--------------------------------------------------------------------------------
/public/niji.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trueai-org/midjourney-proxy-webui/1ceaa67750eb8e4db1e4cb38a3b8f0aca6e53cad/public/niji.webp
--------------------------------------------------------------------------------
/public/scripts/loading.js:
--------------------------------------------------------------------------------
1 | /**
2 | * loading 占位
3 | * 解决首次加载时白屏的问题
4 | */
5 | (function () {
6 | const _root = document.querySelector('#root');
7 | if (_root && _root.innerHTML === '') {
8 | _root.innerHTML = `
9 |
174 |
175 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | 正在加载资源
195 |
196 |
197 | 初次加载资源可能需要较多时间 请耐心等待
198 |
199 |
200 | `;
201 | }
202 | })();
203 |
--------------------------------------------------------------------------------
/src/access.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @see https://umijs.org/zh-CN/plugins/plugin-access
3 | * */
4 | export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
5 | const { currentUser } = initialState ?? {};
6 | return {
7 | canAdmin: currentUser && currentUser.access === 'admin',
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import { SelectLang } from '@/components/RightContent';
2 | import type { Settings as LayoutSettings } from '@ant-design/pro-components';
3 | import { SettingDrawer } from '@ant-design/pro-components';
4 | import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
5 | import { history } from '@umijs/max';
6 | import defaultSettings from '../config/defaultSettings';
7 | import { AvatarDropdown, AvatarName } from './components/RightContent/AvatarDropdown';
8 | import { errorConfig } from './requestErrorConfig';
9 | import { currentUser as queryCurrentUser } from './services/mj/api';
10 |
11 | const isDev = process.env.NODE_ENV === 'development';
12 | const loginPath = '/user/login';
13 |
14 | /**
15 | * @see https://umijs.org/zh-CN/plugins/plugin-initial-state
16 | * */
17 | export async function getInitialState(): Promise<{
18 | settings?: Partial;
19 | currentUser?: API.CurrentUser;
20 | loading?: boolean;
21 | fetchUserInfo?: () => Promise;
22 | }> {
23 | const fetchUserInfo = async () => {
24 | try {
25 | const msg = await queryCurrentUser({
26 | skipErrorHandler: true,
27 | });
28 | return msg;
29 | } catch (error) {
30 | history.push(loginPath);
31 | }
32 | return undefined;
33 | };
34 | // 如果不是登录页面,执行
35 | const { location } = history;
36 | if (location.pathname !== loginPath) {
37 | const currentUser = await fetchUserInfo();
38 | return {
39 | fetchUserInfo,
40 | currentUser,
41 | settings: defaultSettings as Partial,
42 | };
43 | }
44 | return {
45 | fetchUserInfo,
46 | settings: defaultSettings as Partial,
47 | };
48 | }
49 |
50 | // ProLayout 支持的api https://procomponents.ant.design/components/layout
51 | export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
52 | return {
53 | actionsRender: () => [ ],
54 | avatarProps: {
55 | src: initialState?.currentUser?.avatar,
56 | title: ,
57 | render: (_, avatarChildren) => {
58 | return {avatarChildren} ;
59 | },
60 | },
61 |
62 | onPageChange: () => {
63 | const { location } = history;
64 | // 如果没有登录,重定向到 login
65 | if (!initialState?.currentUser && location.pathname !== loginPath) {
66 | history.push(loginPath);
67 | }
68 | },
69 | layoutBgImgList: [
70 | {
71 | src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
72 | left: 85,
73 | bottom: 100,
74 | height: '303px',
75 | },
76 | {
77 | src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
78 | bottom: -68,
79 | right: -45,
80 | height: '303px',
81 | },
82 | {
83 | src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
84 | bottom: 0,
85 | left: 0,
86 | width: '331px',
87 | },
88 | ],
89 | links: [],
90 | menuHeaderRender: undefined,
91 | // 自定义 403 页面
92 | // unAccessible: unAccessible
,
93 | // 增加一个 loading 的状态
94 | childrenRender: (children) => {
95 | // if (initialState?.loading) return ;
96 | return (
97 | <>
98 | {children}
99 | {
104 | setInitialState((preInitialState) => ({
105 | ...preInitialState,
106 | settings,
107 | }));
108 | }}
109 | />
110 | >
111 | );
112 | },
113 | ...initialState?.settings,
114 | };
115 | };
116 |
117 | /**
118 | * @name request 配置,可以配置错误处理
119 | * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
120 | * @doc https://umijs.org/docs/max/request#配置
121 | */
122 | export const request = {
123 | ...errorConfig,
124 | };
--------------------------------------------------------------------------------
/src/components/HeaderDropdown/index.tsx:
--------------------------------------------------------------------------------
1 | import { Dropdown, Tag } from 'antd';
2 | import type { DropDownProps } from 'antd/es/dropdown';
3 | import React from 'react';
4 | import { useEmotionCss } from '@ant-design/use-emotion-css';
5 | import classNames from 'classnames';
6 | import { useModel } from '@umijs/max';
7 |
8 | export type HeaderDropdownProps = {
9 | overlayClassName?: string;
10 | placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
11 | } & Omit;
12 |
13 | const HeaderDropdown: React.FC = ({ overlayClassName: cls, ...restProps }) => {
14 | const { initialState } = useModel('@@initialState');
15 | const { currentUser } = initialState || {};
16 | const className = useEmotionCss(({ token }) => {
17 | return {
18 | [`@media screen and (max-width: ${token.screenXS})`]: {
19 | width: '100%',
20 | },
21 | };
22 | });
23 | return <>{currentUser?.version} >;
24 | };
25 |
26 | export default HeaderDropdown;
27 |
--------------------------------------------------------------------------------
/src/components/JsonEditor/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import AceEditor from 'react-ace';
3 |
4 | import 'ace-builds/src-noconflict/mode-json';
5 | import 'ace-builds/src-noconflict/theme-textmate';
6 |
7 | interface JsonEditorFormItemProps {
8 | value?: object;
9 | onChange?: (value: object) => void;
10 | }
11 |
12 | const JsonEditor: React.FC = ({ value = {}, onChange }) => {
13 | const [textValue, setTextValue] = React.useState(JSON.stringify(value, null, 2));
14 | const [isValidJson, setIsValidJson] = React.useState(true);
15 |
16 | useEffect(() => {
17 | // 只有不等时才更新
18 | const json1 = JSON.stringify(value);
19 | const json2 = JSON.stringify(JSON.parse(textValue));
20 | if (json1 !== json2) {
21 | setTextValue(JSON.stringify(value, null, 2));
22 | }
23 | }, [value]);
24 |
25 | const handleChange = (newValue: string) => {
26 | setTextValue(newValue);
27 | try {
28 | const parsedValue = JSON.parse(newValue);
29 | setIsValidJson(true);
30 | if (onChange) {
31 | onChange(parsedValue);
32 | }
33 | } catch (error) {
34 | setIsValidJson(false);
35 | }
36 | };
37 | return (
38 |
39 |
63 | {!isValidJson && (
64 |
JSON 格式错误,请检查输入!
65 | )}
66 |
67 | );
68 | };
69 |
70 | export default JsonEditor;
71 |
72 | // import React from 'react';
73 | // import ReactJson, { InteractionProps } from 'react-json-view';
74 |
75 | // interface JsonEditorFormItemProps {
76 | // value?: object;
77 | // onChange?: (value: object) => void;
78 | // }
79 |
80 | // const JsonEditor: React.FC = ({ value = {}, onChange }) => {
81 | // const handleEdit = (edit: InteractionProps) => {
82 | // if (onChange) {
83 | // onChange(edit.updated_src);
84 | // }
85 | // };
86 |
87 | // const handleAdd = (add: InteractionProps) => {
88 | // if (onChange) {
89 | // onChange(add.updated_src);
90 | // }
91 | // };
92 |
93 | // const handleDelete = (del: InteractionProps) => {
94 | // if (onChange) {
95 | // onChange(del.updated_src);
96 | // }
97 | // };
98 |
99 | // return (
100 | //
113 | // );
114 | // };
115 |
116 | // export default JsonEditor;
117 |
--------------------------------------------------------------------------------
/src/components/RightContent/AvatarDropdown.tsx:
--------------------------------------------------------------------------------
1 | import { outLogin } from '@/services/mj/api';
2 | import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
3 | import { useEmotionCss } from '@ant-design/use-emotion-css';
4 | import { history, useModel, useIntl } from '@umijs/max';
5 | import { Spin } from 'antd';
6 | import { stringify } from 'querystring';
7 | import type { MenuInfo } from 'rc-menu/lib/interface';
8 | import React, { useCallback } from 'react';
9 | import { flushSync } from 'react-dom';
10 | import HeaderDropdown from '../HeaderDropdown';
11 |
12 | export type GlobalHeaderRightProps = {
13 | menu?: boolean;
14 | children?: React.ReactNode;
15 | };
16 |
17 | export const AvatarName = () => {
18 | const { initialState } = useModel('@@initialState');
19 | const { currentUser } = initialState || {};
20 | return {currentUser?.name} ;
21 | };
22 |
23 | export const AvatarDropdown: React.FC = ({ menu, children }) => {
24 | const intl = useIntl();
25 | /**
26 | * 退出登录,并且将当前的 url 保存
27 | */
28 | const loginOut = async () => {
29 | await outLogin();
30 | const { search, pathname } = window.location;
31 | const urlParams = new URL(window.location.href).searchParams;
32 | /** 此方法会跳转到 redirect 参数所在的位置 */
33 | const redirect = urlParams.get('redirect');
34 | // Note: There may be security issues, please note
35 | if (window.location.pathname !== '/user/login' && !redirect) {
36 | history.replace({
37 | pathname: '/user/login',
38 | search: stringify({
39 | redirect: pathname + search,
40 | }),
41 | });
42 | }
43 | };
44 | const actionClassName = useEmotionCss(({ token }) => {
45 | return {
46 | display: 'flex',
47 | height: '48px',
48 | marginLeft: 'auto',
49 | overflow: 'hidden',
50 | alignItems: 'center',
51 | padding: '0 8px',
52 | cursor: 'pointer',
53 | borderRadius: token.borderRadius,
54 | '&:hover': {
55 | backgroundColor: token.colorBgTextHover,
56 | },
57 | };
58 | });
59 | const { initialState, setInitialState } = useModel('@@initialState');
60 |
61 | const onMenuClick = useCallback(
62 | (event: MenuInfo) => {
63 | const { key } = event;
64 | if (key === 'logout') {
65 | flushSync(() => {
66 | setInitialState((s) => ({ ...s, currentUser: undefined }));
67 | });
68 | loginOut();
69 | return;
70 | }
71 | history.push(`/account/${key}`);
72 | },
73 | [setInitialState],
74 | );
75 |
76 | const loading = (
77 |
78 |
85 |
86 | );
87 |
88 | if (!initialState) {
89 | return loading;
90 | }
91 |
92 | const { currentUser } = initialState;
93 |
94 | if (!currentUser || !currentUser.name) {
95 | return loading;
96 | }
97 |
98 | const menuItems = [
99 | ...(menu
100 | ? [
101 | {
102 | key: 'center',
103 | icon: ,
104 | label: '个人中心',
105 | },
106 | {
107 | key: 'settings',
108 | icon: ,
109 | label: '个人设置',
110 | },
111 | {
112 | type: 'divider' as const,
113 | },
114 | ]
115 | : []),
116 | {
117 | key: 'logout',
118 | icon: ,
119 | label: intl.formatMessage({ id: 'menu.account.logout' }),
120 | },
121 | ];
122 |
123 | return (
124 |
131 | {children}
132 |
133 | );
134 | };
135 |
--------------------------------------------------------------------------------
/src/components/RightContent/index.tsx:
--------------------------------------------------------------------------------
1 | import { QuestionCircleOutlined } from '@ant-design/icons';
2 | import { SelectLang as UmiSelectLang } from '@umijs/max';
3 | import React from 'react';
4 |
5 | export type SiderTheme = 'light' | 'dark';
6 |
7 | export const SelectLang = () => {
8 | return (
9 |
14 | );
15 | };
16 |
17 | export const Question = () => {
18 | return (
19 | {
25 | window.open('https://pro.ant.design/docs/getting-started');
26 | }}
27 | >
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/global.less:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #root {
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
8 | 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
9 | 'Noto Color Emoji';
10 | }
11 |
12 | .colorWeak {
13 | filter: invert(80%);
14 | }
15 |
16 | .ant-layout {
17 | min-height: 100vh;
18 | }
19 |
20 | .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
21 | left: unset;
22 | }
23 |
24 | canvas {
25 | display: block;
26 | }
27 |
28 | body {
29 | text-rendering: optimizeLegibility;
30 | -webkit-font-smoothing: antialiased;
31 | -moz-osx-font-smoothing: grayscale;
32 | }
33 |
34 | ul,
35 | ol {
36 | list-style: none;
37 | }
38 |
39 | @media (max-width: 768px) {
40 | .ant-table {
41 | width: 100%;
42 | overflow-x: auto;
43 |
44 | &-thead>tr,
45 | &-tbody>tr {
46 |
47 | >th,
48 | >td {
49 | white-space: pre;
50 |
51 | >span {
52 | display: block;
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
59 | // .ant-pro-form-login-top{
60 | // padding: 0 30vw;
61 | // }
62 |
63 | .ant-pro-form-login-header {
64 | height: unset !important;
65 | }
66 |
67 |
68 | #subChannels_extra {
69 | position: absolute !important;
70 | right: 0;
71 | top: 0;
72 | }
--------------------------------------------------------------------------------
/src/global.tsx:
--------------------------------------------------------------------------------
1 | import { useIntl } from '@umijs/max';
2 | import { Button, message, notification } from 'antd';
3 | import defaultSettings from '../config/defaultSettings';
4 |
5 | const { pwa } = defaultSettings;
6 | const isHttps = document.location.protocol === 'https:';
7 |
8 | const clearCache = () => {
9 | // remove all caches
10 | if (window.caches) {
11 | caches
12 | .keys()
13 | .then((keys) => {
14 | keys.forEach((key) => {
15 | caches.delete(key);
16 | });
17 | })
18 | .catch((e) => console.log(e));
19 | }
20 | };
21 |
22 | // if pwa is true
23 | if (pwa) {
24 | // Notify user if offline now
25 | window.addEventListener('sw.offline', () => {
26 | message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' }));
27 | });
28 |
29 | // Pop up a prompt on the page asking the user if they want to use the latest version
30 | window.addEventListener('sw.updated', (event: Event) => {
31 | const e = event as CustomEvent;
32 | const reloadSW = async () => {
33 | // Check if there is sw whose state is waiting in ServiceWorkerRegistration
34 | // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
35 | const worker = e.detail && e.detail.waiting;
36 | if (!worker) {
37 | return true;
38 | }
39 | // Send skip-waiting event to waiting SW with MessageChannel
40 | await new Promise((resolve, reject) => {
41 | const channel = new MessageChannel();
42 | channel.port1.onmessage = (msgEvent) => {
43 | if (msgEvent.data.error) {
44 | reject(msgEvent.data.error);
45 | } else {
46 | resolve(msgEvent.data);
47 | }
48 | };
49 | worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
50 | });
51 |
52 | clearCache();
53 | window.location.reload();
54 | return true;
55 | };
56 | const key = `open${Date.now()}`;
57 | const btn = (
58 | {
61 | notification.destroy(key);
62 | reloadSW();
63 | }}
64 | >
65 | {useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
66 |
67 | );
68 | notification.open({
69 | message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
70 | description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
71 | btn,
72 | key,
73 | onClose: async () => null,
74 | });
75 | });
76 | } else if ('serviceWorker' in navigator && isHttps) {
77 | // unregister service worker
78 | const { serviceWorker } = navigator;
79 | if (serviceWorker.getRegistrations) {
80 | serviceWorker.getRegistrations().then((sws) => {
81 | sws.forEach((sw) => {
82 | sw.unregister();
83 | });
84 | });
85 | }
86 | serviceWorker.getRegistration().then((sw) => {
87 | if (sw) sw.unregister();
88 | });
89 |
90 | clearCache();
91 | }
92 |
--------------------------------------------------------------------------------
/src/locales/en-US.ts:
--------------------------------------------------------------------------------
1 | import component from './en-US/component';
2 | import globalHeader from './en-US/globalHeader';
3 | import menu from './en-US/menu';
4 | import pages from './en-US/pages';
5 | import pwa from './en-US/pwa';
6 | import settingDrawer from './en-US/settingDrawer';
7 | import settings from './en-US/settings';
8 |
9 | export default {
10 | 'navBar.lang': 'Languages',
11 | 'layout.user.link.help': 'Help',
12 | 'layout.user.link.privacy': 'Privacy',
13 | 'layout.user.link.terms': 'Terms',
14 | 'app.copyright.produced': 'Midjourney Proxy Admin',
15 | 'app.preview.down.block': 'Download this page to your local project',
16 | 'app.welcome.link.fetch-blocks': 'Get all block',
17 | 'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
18 | ...globalHeader,
19 | ...menu,
20 | ...settingDrawer,
21 | ...settings,
22 | ...pwa,
23 | ...component,
24 | ...pages,
25 | };
26 |
--------------------------------------------------------------------------------
/src/locales/en-US/component.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.tagSelect.expand': 'Expand',
3 | 'component.tagSelect.collapse': 'Collapse',
4 | 'component.tagSelect.all': 'All',
5 | };
6 |
--------------------------------------------------------------------------------
/src/locales/en-US/globalHeader.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.globalHeader.search': 'Search',
3 | 'component.globalHeader.search.example1': 'Search example 1',
4 | 'component.globalHeader.search.example2': 'Search example 2',
5 | 'component.globalHeader.search.example3': 'Search example 3',
6 | 'component.globalHeader.help': 'Help',
7 | 'component.globalHeader.notification': 'Notification',
8 | 'component.globalHeader.notification.empty': 'You have viewed all notifications.',
9 | 'component.globalHeader.message': 'Message',
10 | 'component.globalHeader.message.empty': 'You have viewed all messsages.',
11 | 'component.globalHeader.event': 'Event',
12 | 'component.globalHeader.event.empty': 'You have viewed all events.',
13 | 'component.noticeIcon.clear': 'Clear',
14 | 'component.noticeIcon.cleared': 'Cleared',
15 | 'component.noticeIcon.empty': 'No notifications',
16 | 'component.noticeIcon.view-more': 'View more',
17 | };
18 |
--------------------------------------------------------------------------------
/src/locales/en-US/menu.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.welcome': 'Welcome',
3 | 'menu.more-blocks': 'More Blocks',
4 | 'menu.home': 'Home',
5 | 'menu.admin': 'Admin',
6 | 'menu.admin.sub-page': 'Sub-Page',
7 | 'menu.login': 'Login',
8 | 'menu.register': 'Register',
9 | 'menu.register-result': 'Register Result',
10 | 'menu.dashboard': 'Dashboard',
11 | 'menu.dashboard.analysis': 'Analysis',
12 | 'menu.dashboard.monitor': 'Monitor',
13 | 'menu.dashboard.workplace': 'Workplace',
14 | 'menu.exception.403': '403',
15 | 'menu.exception.404': '404',
16 | 'menu.exception.500': '500',
17 | 'menu.form': 'Form',
18 | 'menu.form.basic-form': 'Basic Form',
19 | 'menu.form.step-form': 'Step Form',
20 | 'menu.form.step-form.info': 'Step Form(write transfer information)',
21 | 'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
22 | 'menu.form.step-form.result': 'Step Form(finished)',
23 | 'menu.form.advanced-form': 'Advanced Form',
24 | 'menu.list': 'List',
25 | 'menu.list.table-list': 'Search Table',
26 | 'menu.list.account-list': 'Account Manage',
27 | 'menu.list.domain-list': 'Domain Manage',
28 | 'menu.list.banned-word-list': 'Banned Word Manage',
29 | 'menu.list.user-list': 'User Manage',
30 | 'menu.api-doc': 'API DOC',
31 | 'menu.task-list': 'Task Search',
32 | 'menu.draw-test': 'Draw Test',
33 | 'menu.setting': 'System Setting',
34 | 'menu.probe': 'Log Probe',
35 | 'menu.activate': 'System Activate',
36 | 'menu.list.basic-list': 'Basic List',
37 | 'menu.list.card-list': 'Card List',
38 | 'menu.list.search-list': 'Search List',
39 | 'menu.list.search-list.articles': 'Search List(articles)',
40 | 'menu.list.search-list.projects': 'Search List(projects)',
41 | 'menu.list.search-list.applications': 'Search List(applications)',
42 | 'menu.profile': 'Profile',
43 | 'menu.profile.basic': 'Basic Profile',
44 | 'menu.profile.advanced': 'Advanced Profile',
45 | 'menu.result': 'Result',
46 | 'menu.result.success': 'Success',
47 | 'menu.result.fail': 'Fail',
48 | 'menu.exception': 'Exception',
49 | 'menu.exception.not-permission': '403',
50 | 'menu.exception.not-find': '404',
51 | 'menu.exception.server-error': '500',
52 | 'menu.exception.trigger': 'Trigger',
53 | 'menu.account': 'Account',
54 | 'menu.account.center': 'Account Center',
55 | 'menu.account.settings': 'Account Settings',
56 | 'menu.account.trigger': 'Trigger Error',
57 | 'menu.account.logout': 'Logout',
58 | 'menu.editor': 'Graphic Editor',
59 | 'menu.editor.flow': 'Flow Editor',
60 | 'menu.editor.mind': 'Mind Editor',
61 | 'menu.editor.koni': 'Koni Editor',
62 | };
63 |
--------------------------------------------------------------------------------
/src/locales/en-US/pwa.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.pwa.offline': 'You are offline now',
3 | 'app.pwa.serviceworker.updated': 'New content is available',
4 | 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
5 | 'app.pwa.serviceworker.updated.ok': 'Refresh',
6 | };
7 |
--------------------------------------------------------------------------------
/src/locales/en-US/settingDrawer.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.setting.pagestyle': 'Page style setting',
3 | 'app.setting.pagestyle.dark': 'Dark style',
4 | 'app.setting.pagestyle.light': 'Light style',
5 | 'app.setting.content-width': 'Content Width',
6 | 'app.setting.content-width.fixed': 'Fixed',
7 | 'app.setting.content-width.fluid': 'Fluid',
8 | 'app.setting.themecolor': 'Theme Color',
9 | 'app.setting.themecolor.dust': 'Dust Red',
10 | 'app.setting.themecolor.volcano': 'Volcano',
11 | 'app.setting.themecolor.sunset': 'Sunset Orange',
12 | 'app.setting.themecolor.cyan': 'Cyan',
13 | 'app.setting.themecolor.green': 'Polar Green',
14 | 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
15 | 'app.setting.themecolor.geekblue': 'Geek Glue',
16 | 'app.setting.themecolor.purple': 'Golden Purple',
17 | 'app.setting.navigationmode': 'Navigation Mode',
18 | 'app.setting.sidemenu': 'Side Menu Layout',
19 | 'app.setting.topmenu': 'Top Menu Layout',
20 | 'app.setting.fixedheader': 'Fixed Header',
21 | 'app.setting.fixedsidebar': 'Fixed Sidebar',
22 | 'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
23 | 'app.setting.hideheader': 'Hidden Header when scrolling',
24 | 'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
25 | 'app.setting.othersettings': 'Other Settings',
26 | 'app.setting.weakmode': 'Weak Mode',
27 | 'app.setting.copy': 'Copy Setting',
28 | 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
29 | 'app.setting.production.hint':
30 | 'Setting panel shows in development environment only, please manually modify',
31 | };
32 |
--------------------------------------------------------------------------------
/src/locales/en-US/settings.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.settings.menuMap.basic': 'Basic Settings',
3 | 'app.settings.menuMap.security': 'Security Settings',
4 | 'app.settings.menuMap.binding': 'Account Binding',
5 | 'app.settings.menuMap.notification': 'New Message Notification',
6 | 'app.settings.basic.avatar': 'Avatar',
7 | 'app.settings.basic.change-avatar': 'Change avatar',
8 | 'app.settings.basic.email': 'Email',
9 | 'app.settings.basic.email-message': 'Please input your email!',
10 | 'app.settings.basic.nickname': 'Nickname',
11 | 'app.settings.basic.nickname-message': 'Please input your Nickname!',
12 | 'app.settings.basic.profile': 'Personal profile',
13 | 'app.settings.basic.profile-message': 'Please input your personal profile!',
14 | 'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
15 | 'app.settings.basic.country': 'Country/Region',
16 | 'app.settings.basic.country-message': 'Please input your country!',
17 | 'app.settings.basic.geographic': 'Province or city',
18 | 'app.settings.basic.geographic-message': 'Please input your geographic info!',
19 | 'app.settings.basic.address': 'Street Address',
20 | 'app.settings.basic.address-message': 'Please input your address!',
21 | 'app.settings.basic.phone': 'Phone Number',
22 | 'app.settings.basic.phone-message': 'Please input your phone!',
23 | 'app.settings.basic.update': 'Update Information',
24 | 'app.settings.security.strong': 'Strong',
25 | 'app.settings.security.medium': 'Medium',
26 | 'app.settings.security.weak': 'Weak',
27 | 'app.settings.security.password': 'Account Password',
28 | 'app.settings.security.password-description': 'Current password strength',
29 | 'app.settings.security.phone': 'Security Phone',
30 | 'app.settings.security.phone-description': 'Bound phone',
31 | 'app.settings.security.question': 'Security Question',
32 | 'app.settings.security.question-description':
33 | 'The security question is not set, and the security policy can effectively protect the account security',
34 | 'app.settings.security.email': 'Backup Email',
35 | 'app.settings.security.email-description': 'Bound Email',
36 | 'app.settings.security.mfa': 'MFA Device',
37 | 'app.settings.security.mfa-description':
38 | 'Unbound MFA device, after binding, can be confirmed twice',
39 | 'app.settings.security.modify': 'Modify',
40 | 'app.settings.security.set': 'Set',
41 | 'app.settings.security.bind': 'Bind',
42 | 'app.settings.binding.taobao': 'Binding Taobao',
43 | 'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
44 | 'app.settings.binding.alipay': 'Binding Alipay',
45 | 'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
46 | 'app.settings.binding.dingding': 'Binding DingTalk',
47 | 'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
48 | 'app.settings.binding.bind': 'Bind',
49 | 'app.settings.notification.password': 'Account Password',
50 | 'app.settings.notification.password-description':
51 | 'Messages from other users will be notified in the form of a station letter',
52 | 'app.settings.notification.messages': 'System Messages',
53 | 'app.settings.notification.messages-description':
54 | 'System messages will be notified in the form of a station letter',
55 | 'app.settings.notification.todo': 'To-do Notification',
56 | 'app.settings.notification.todo-description':
57 | 'The to-do list will be notified in the form of a letter from the station',
58 | 'app.settings.open': 'Open',
59 | 'app.settings.close': 'Close',
60 | };
61 |
--------------------------------------------------------------------------------
/src/locales/zh-CN.ts:
--------------------------------------------------------------------------------
1 | import component from './zh-CN/component';
2 | import globalHeader from './zh-CN/globalHeader';
3 | import menu from './zh-CN/menu';
4 | import pages from './zh-CN/pages';
5 | import pwa from './zh-CN/pwa';
6 | import settingDrawer from './zh-CN/settingDrawer';
7 | import settings from './zh-CN/settings';
8 |
9 | export default {
10 | 'navBar.lang': '语言',
11 | 'layout.user.link.help': '帮助',
12 | 'layout.user.link.privacy': '隐私',
13 | 'layout.user.link.terms': '条款',
14 | 'app.copyright.produced': 'Midjourney Proxy Admin',
15 | 'app.preview.down.block': '下载此页面到本地项目',
16 | 'app.welcome.link.fetch-blocks': '获取全部区块',
17 | 'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
18 | ...pages,
19 | ...globalHeader,
20 | ...menu,
21 | ...settingDrawer,
22 | ...settings,
23 | ...pwa,
24 | ...component,
25 | };
26 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/component.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.tagSelect.expand': '展开',
3 | 'component.tagSelect.collapse': '收起',
4 | 'component.tagSelect.all': '全部',
5 | };
6 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/globalHeader.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'component.globalHeader.search': '站内搜索',
3 | 'component.globalHeader.search.example1': '搜索提示一',
4 | 'component.globalHeader.search.example2': '搜索提示二',
5 | 'component.globalHeader.search.example3': '搜索提示三',
6 | 'component.globalHeader.help': '使用文档',
7 | 'component.globalHeader.notification': '通知',
8 | 'component.globalHeader.notification.empty': '你已查看所有通知',
9 | 'component.globalHeader.message': '消息',
10 | 'component.globalHeader.message.empty': '您已读完所有消息',
11 | 'component.globalHeader.event': '待办',
12 | 'component.globalHeader.event.empty': '你已完成所有待办',
13 | 'component.noticeIcon.clear': '清空',
14 | 'component.noticeIcon.cleared': '清空了',
15 | 'component.noticeIcon.empty': '暂无数据',
16 | 'component.noticeIcon.view-more': '查看更多',
17 | };
18 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/menu.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.welcome': '欢迎',
3 | 'menu.more-blocks': '更多区块',
4 | 'menu.home': '首页',
5 | 'menu.admin': '管理页',
6 | 'menu.admin.sub-page': '二级管理页',
7 | 'menu.login': '登录',
8 | 'menu.register': '注册',
9 | 'menu.register-result': '注册结果',
10 | 'menu.dashboard': 'Dashboard',
11 | 'menu.dashboard.analysis': '分析页',
12 | 'menu.dashboard.monitor': '监控页',
13 | 'menu.dashboard.workplace': '工作台',
14 | 'menu.exception.403': '403',
15 | 'menu.exception.404': '404',
16 | 'menu.exception.500': '500',
17 | 'menu.form': '表单页',
18 | 'menu.form.basic-form': '基础表单',
19 | 'menu.form.step-form': '分步表单',
20 | 'menu.form.step-form.info': '分步表单(填写转账信息)',
21 | 'menu.form.step-form.confirm': '分步表单(确认转账信息)',
22 | 'menu.form.step-form.result': '分步表单(完成)',
23 | 'menu.form.advanced-form': '高级表单',
24 | 'menu.list': '列表页',
25 | 'menu.list.table-list': '查询表格',
26 | 'menu.list.account-list': '账号管理',
27 | 'menu.list.domain-list': '领域管理',
28 | 'menu.list.banned-word-list': '违规词条',
29 | 'menu.list.user-list': '用户管理',
30 | 'menu.task-list': '任务查询',
31 | 'menu.draw-test': '绘画测试',
32 | 'menu.setting': '系统设置',
33 | 'menu.probe': '日志查看',
34 | 'menu.activate': '服务激活',
35 | 'menu.api-doc': 'API文档',
36 | 'menu.list.basic-list': '标准列表',
37 | 'menu.list.card-list': '卡片列表',
38 | 'menu.list.search-list': '搜索列表',
39 | 'menu.list.search-list.articles': '搜索列表(文章)',
40 | 'menu.list.search-list.projects': '搜索列表(项目)',
41 | 'menu.list.search-list.applications': '搜索列表(应用)',
42 | 'menu.profile': '详情页',
43 | 'menu.profile.basic': '基础详情页',
44 | 'menu.profile.advanced': '高级详情页',
45 | 'menu.result': '结果页',
46 | 'menu.result.success': '成功页',
47 | 'menu.result.fail': '失败页',
48 | 'menu.exception': '异常页',
49 | 'menu.exception.not-permission': '403',
50 | 'menu.exception.not-find': '404',
51 | 'menu.exception.server-error': '500',
52 | 'menu.exception.trigger': '触发错误',
53 | 'menu.account': '个人页',
54 | 'menu.account.center': '个人中心',
55 | 'menu.account.settings': '个人设置',
56 | 'menu.account.trigger': '触发报错',
57 | 'menu.account.logout': '退出登录',
58 | 'menu.editor': '图形编辑器',
59 | 'menu.editor.flow': '流程编辑器',
60 | 'menu.editor.mind': '脑图编辑器',
61 | 'menu.editor.koni': '拓扑编辑器',
62 | };
63 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/pwa.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.pwa.offline': '当前处于离线状态',
3 | 'app.pwa.serviceworker.updated': '有新内容',
4 | 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
5 | 'app.pwa.serviceworker.updated.ok': '刷新',
6 | };
7 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/settingDrawer.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.setting.pagestyle': '整体风格设置',
3 | 'app.setting.pagestyle.dark': '暗色菜单风格',
4 | 'app.setting.pagestyle.light': '亮色菜单风格',
5 | 'app.setting.content-width': '内容区域宽度',
6 | 'app.setting.content-width.fixed': '定宽',
7 | 'app.setting.content-width.fluid': '流式',
8 | 'app.setting.themecolor': '主题色',
9 | 'app.setting.themecolor.dust': '薄暮',
10 | 'app.setting.themecolor.volcano': '火山',
11 | 'app.setting.themecolor.sunset': '日暮',
12 | 'app.setting.themecolor.cyan': '明青',
13 | 'app.setting.themecolor.green': '极光绿',
14 | 'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
15 | 'app.setting.themecolor.geekblue': '极客蓝',
16 | 'app.setting.themecolor.purple': '酱紫',
17 | 'app.setting.navigationmode': '导航模式',
18 | 'app.setting.sidemenu': '侧边菜单布局',
19 | 'app.setting.topmenu': '顶部菜单布局',
20 | 'app.setting.fixedheader': '固定 Header',
21 | 'app.setting.fixedsidebar': '固定侧边菜单',
22 | 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
23 | 'app.setting.hideheader': '下滑时隐藏 Header',
24 | 'app.setting.hideheader.hint': '固定 Header 时可配置',
25 | 'app.setting.othersettings': '其他设置',
26 | 'app.setting.weakmode': '色弱模式',
27 | 'app.setting.copy': '拷贝设置',
28 | 'app.setting.copyinfo': '拷贝成功,请到 config/defaultSettings.js 中替换默认配置',
29 | 'app.setting.production.hint':
30 | '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
31 | };
32 |
--------------------------------------------------------------------------------
/src/locales/zh-CN/settings.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'app.settings.menuMap.basic': '基本设置',
3 | 'app.settings.menuMap.security': '安全设置',
4 | 'app.settings.menuMap.binding': '账号绑定',
5 | 'app.settings.menuMap.notification': '新消息通知',
6 | 'app.settings.basic.avatar': '头像',
7 | 'app.settings.basic.change-avatar': '更换头像',
8 | 'app.settings.basic.email': '邮箱',
9 | 'app.settings.basic.email-message': '请输入您的邮箱!',
10 | 'app.settings.basic.nickname': '昵称',
11 | 'app.settings.basic.nickname-message': '请输入您的昵称!',
12 | 'app.settings.basic.profile': '个人简介',
13 | 'app.settings.basic.profile-message': '请输入个人简介!',
14 | 'app.settings.basic.profile-placeholder': '个人简介',
15 | 'app.settings.basic.country': '国家/地区',
16 | 'app.settings.basic.country-message': '请输入您的国家或地区!',
17 | 'app.settings.basic.geographic': '所在省市',
18 | 'app.settings.basic.geographic-message': '请输入您的所在省市!',
19 | 'app.settings.basic.address': '街道地址',
20 | 'app.settings.basic.address-message': '请输入您的街道地址!',
21 | 'app.settings.basic.phone': '联系电话',
22 | 'app.settings.basic.phone-message': '请输入您的联系电话!',
23 | 'app.settings.basic.update': '更新基本信息',
24 | 'app.settings.security.strong': '强',
25 | 'app.settings.security.medium': '中',
26 | 'app.settings.security.weak': '弱',
27 | 'app.settings.security.password': '账户密码',
28 | 'app.settings.security.password-description': '当前密码强度',
29 | 'app.settings.security.phone': '密保手机',
30 | 'app.settings.security.phone-description': '已绑定手机',
31 | 'app.settings.security.question': '密保问题',
32 | 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
33 | 'app.settings.security.email': '备用邮箱',
34 | 'app.settings.security.email-description': '已绑定邮箱',
35 | 'app.settings.security.mfa': 'MFA 设备',
36 | 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
37 | 'app.settings.security.modify': '修改',
38 | 'app.settings.security.set': '设置',
39 | 'app.settings.security.bind': '绑定',
40 | 'app.settings.binding.taobao': '绑定淘宝',
41 | 'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
42 | 'app.settings.binding.alipay': '绑定支付宝',
43 | 'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
44 | 'app.settings.binding.dingding': '绑定钉钉',
45 | 'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
46 | 'app.settings.binding.bind': '绑定',
47 | 'app.settings.notification.password': '账户密码',
48 | 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
49 | 'app.settings.notification.messages': '系统消息',
50 | 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
51 | 'app.settings.notification.todo': '待办任务',
52 | 'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
53 | 'app.settings.open': '开',
54 | 'app.settings.close': '关',
55 | };
56 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Ant Design Pro",
3 | "short_name": "Ant Design Pro",
4 | "display": "standalone",
5 | "start_url": "./?utm_source=homescreen",
6 | "theme_color": "#002140",
7 | "background_color": "#001529",
8 | "icons": [
9 | {
10 | "src": "icons/icon-192x192.png",
11 | "sizes": "192x192"
12 | },
13 | {
14 | "src": "icons/icon-128x128.png",
15 | "sizes": "128x128"
16 | },
17 | {
18 | "src": "icons/icon-512x512.png",
19 | "sizes": "512x512"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import { history } from '@umijs/max';
2 | import { Button, Result } from 'antd';
3 | import React from 'react';
4 |
5 | const NoFoundPage: React.FC = () => (
6 | history.push('/')}>
12 | Back Home
13 |
14 | }
15 | />
16 | );
17 |
18 | export default NoFoundPage;
19 |
--------------------------------------------------------------------------------
/src/pages/AccountList/components/button/DelButton.tsx:
--------------------------------------------------------------------------------
1 | import { deleteAccount } from '@/services/mj/api';
2 | import { Button, Popconfirm, notification } from 'antd';
3 | import { DeleteOutlined } from '@ant-design/icons';
4 | import React, { useState } from 'react';
5 | import { useIntl } from '@umijs/max';
6 |
7 | // 定义 DelButton 接受的 props 类型
8 | interface DelButtonProps {
9 | record: Record;
10 | onSuccess: () => void;
11 | }
12 |
13 | const DelButton: React.FC = ({ record, onSuccess }) => {
14 | const [open, setOpen] = useState(false);
15 | const [confirmLoading, setConfirmLoading] = useState(false);
16 | const [api, contextHolder] = notification.useNotification();
17 | const intl = useIntl();
18 |
19 | const showPopconfirm = () => {
20 | setOpen(true);
21 | };
22 |
23 | const handleOk = async () => {
24 | setConfirmLoading(true);
25 | try {
26 | const res = await deleteAccount(record.id);
27 | setOpen(false);
28 | if (res.success) {
29 | api.success({
30 | message: 'success',
31 | description: intl.formatMessage({ id: 'pages.account.deleteSuccess' })
32 | });
33 | onSuccess();
34 | } else {
35 | api.error({
36 | message: 'error',
37 | description: res.message
38 | });
39 | }
40 | } catch (error) {
41 | console.error(error);
42 | } finally {
43 | setConfirmLoading(false);
44 | }
45 | };
46 |
47 | const handleCancel = () => {
48 | setOpen(false);
49 | };
50 |
51 | return (
52 |
60 | {contextHolder}
61 | } onClick={showPopconfirm}>
62 |
63 |
64 | );
65 | };
66 |
67 | export default DelButton;
68 |
--------------------------------------------------------------------------------
/src/pages/AccountList/components/button/SyncButton.tsx:
--------------------------------------------------------------------------------
1 | import { refreshAccount } from '@/services/mj/api';
2 | import { SyncOutlined } from '@ant-design/icons';
3 | import { useIntl } from '@umijs/max';
4 | import { Button, notification, Popconfirm } from 'antd';
5 | import React, { useState } from 'react';
6 |
7 | interface SyncButtonProps {
8 | record: Record;
9 | onSuccess: () => void;
10 | }
11 |
12 | const SyncButton: React.FC = ({ record, onSuccess }) => {
13 | const [open, setOpen] = useState(false);
14 | const [confirmLoading, setConfirmLoading] = useState(false);
15 | const [api, contextHolder] = notification.useNotification();
16 | const intl = useIntl();
17 |
18 | const showPopconfirm = () => {
19 | setOpen(true);
20 | };
21 |
22 | const handleOk = async () => {
23 | setConfirmLoading(true);
24 | try {
25 | const res = await refreshAccount(record.id);
26 | console.log('resss', res);
27 |
28 | setOpen(false);
29 | if (res.success) {
30 | api.success({
31 | message: 'success',
32 | description: intl.formatMessage({ id: 'pages.account.syncSuccess' }),
33 | });
34 | onSuccess();
35 | } else {
36 | api.error({
37 | message: 'error',
38 | description: res.message,
39 | });
40 | onSuccess();
41 | }
42 | } catch (error) {
43 | console.error(error);
44 | } finally {
45 | setConfirmLoading(false);
46 | }
47 | };
48 |
49 | const handleCancel = () => {
50 | setOpen(false);
51 | };
52 |
53 | return (
54 |
62 | {contextHolder}
63 | } onClick={showPopconfirm}>
64 | {/* {intl.formatMessage({ id: 'pages.account.sync' })} */}
65 |
66 |
67 | );
68 | };
69 |
70 | export default SyncButton;
71 |
--------------------------------------------------------------------------------
/src/pages/AccountList/components/contents/CfContent.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Col, Form, FormInstance, notification, Row, Space } from 'antd';
2 | import { useEffect, useState } from 'react';
3 |
4 | import { accountCfOk, accountCfUrl } from '@/services/mj/api';
5 | import { useIntl } from '@umijs/max';
6 |
7 | const CfContent = ({
8 | form,
9 | onSubmit,
10 | record,
11 | }: {
12 | form: FormInstance;
13 | onSubmit: (values: any) => void;
14 | record: Record;
15 | }) => {
16 | const intl = useIntl();
17 |
18 | const [api, contextHolder] = notification.useNotification();
19 | const [data, setData] = useState>();
20 | const [loading, setLoading] = useState(false);
21 |
22 | // 当组件挂载或者record更新时,设置表单的初始值
23 | useEffect(() => {
24 | setData(record);
25 | }, []);
26 |
27 | const updateUrl = async () => {
28 | setLoading(true);
29 | const res = await accountCfUrl(record.id);
30 | setLoading(false);
31 | if (res.success) {
32 | setData(res.data);
33 | }
34 | };
35 |
36 | const updateOk = async () => {
37 | setLoading(true);
38 | const res = await accountCfOk(record.id);
39 | setLoading(false);
40 | if (res.success) {
41 | api.success({
42 | message: 'success',
43 | description: 'Success',
44 | });
45 | onSubmit(data);
46 | }
47 | };
48 |
49 | return (
50 |
80 | );
81 | };
82 |
83 | export default CfContent;
84 |
--------------------------------------------------------------------------------
/src/pages/AccountList/components/contents/MoreContent.tsx:
--------------------------------------------------------------------------------
1 | import { accountAction, accountChangeVersion } from '@/services/mj/api';
2 | import { useIntl } from '@umijs/max';
3 | import { Button, Card, Descriptions, notification, Select, Space, Tag, Tooltip } from 'antd';
4 | import moment from 'moment';
5 | import React, { useEffect, useState } from 'react';
6 |
7 | const { Option } = Select;
8 |
9 | interface MoreContentProps {
10 | record: Record;
11 | onSuccess: () => void;
12 | }
13 |
14 | const MoreContent: React.FC = ({ record, onSuccess }) => {
15 | const [api, contextHolder] = notification.useNotification();
16 | const [version, setVersion] = useState('');
17 | const [buttons, setButtons] = useState>([]);
18 | const [nijiButtons, setNijiButtons] = useState>([]);
19 | const [loading, setLoading] = useState(false);
20 | const [loadingButton, setLoadingButton] = useState('');
21 | const intl = useIntl();
22 |
23 | useEffect(() => {
24 | setVersion(record.version);
25 | setButtons(record.buttons);
26 | setNijiButtons(record.nijiButtons);
27 | }, [record]);
28 |
29 | const getStatusTag = (enable: boolean, enableText: string, disableText: string) => {
30 | let color = enable ? 'green' : 'volcano';
31 | let text = enable ? enableText : disableText;
32 | return {text} ;
33 | };
34 |
35 | const getTooltip = (text: string) => {
36 | if (!text || text.length < 25) return text;
37 | return {text.substring(0, 25) + '...'} ;
38 | };
39 |
40 | const changeDate = (date: string) => {
41 | return moment(date).format('YYYY-MM-DD HH:mm');
42 | };
43 |
44 | const versionSelectorOptions = () => {
45 | return record.versionSelector?.map((option: any) => {
46 | return (
47 |
48 | {option.emoji} {option.label}
49 |
50 | );
51 | });
52 | };
53 |
54 | const accountButtons = () => {
55 | return buttons.map((button: any) => {
56 | return (
57 | {
62 | action('MID_JOURNEY', button.customId);
63 | }}
64 | loading={loadingButton == 'MID_JOURNEY:' + button.customId}
65 | >
66 | {button.emoji} {button.label}
67 |
68 | );
69 | });
70 | };
71 |
72 | const accountNijiButtons = () => {
73 | return nijiButtons.map((button: any) => {
74 | return (
75 | {
80 | action('NIJI_JOURNEY', button.customId);
81 | }}
82 | loading={loadingButton == 'NIJI_JOURNEY:' + button.customId}
83 | >
84 | {button.emoji} {button.label}
85 |
86 | );
87 | });
88 | };
89 |
90 | const versionChange = async (value: string) => {
91 | setVersion(value);
92 | setLoading(true);
93 | const res = await accountChangeVersion(record.id, value);
94 | if (res.success) {
95 | setLoading(false);
96 | api.success({
97 | message: 'success',
98 | description: intl.formatMessage({ id: 'pages.account.mjVersionSuccess' }),
99 | });
100 | onSuccess();
101 | } else {
102 | setVersion(record.version);
103 | setLoading(false);
104 | api.error({
105 | message: 'error',
106 | description: res.message,
107 | });
108 | }
109 | };
110 |
111 | const action = async (botType: string, customId: string) => {
112 | if (loadingButton !== '') return;
113 | setLoadingButton(botType + ':' + customId);
114 | const res = await accountAction(record.id, botType, customId);
115 | setLoadingButton('');
116 | if (res.success) {
117 | onSuccess();
118 | } else {
119 | api.error({
120 | message: 'error',
121 | description: res.message,
122 | });
123 | }
124 | };
125 |
126 | return (
127 | <>
128 | {contextHolder}
129 |
134 |
135 |
136 | {record.guildId}
137 |
138 |
139 | {record.channelId}
140 |
141 | {/*
142 | {record.name}
143 | */}
144 |
145 | {getTooltip(record.userToken)}
146 |
147 |
148 | {getTooltip(record.botToken)}
149 |
150 | {getTooltip(record.userAgent)}
151 |
152 | {getStatusTag(
153 | record.remixAutoSubmit,
154 | intl.formatMessage({ id: 'pages.yes' }),
155 | intl.formatMessage({ id: 'pages.no' }),
156 | )}
157 |
158 |
159 | {record.privateChannelId}
160 |
161 |
162 | {record.nijiBotChannelId}
163 |
164 |
165 |
166 |
167 |
172 |
173 |
174 | {getStatusTag(
175 | record.enable,
176 | intl.formatMessage({ id: 'pages.enable' }),
177 | intl.formatMessage({ id: 'pages.disable' }),
178 | )}
179 |
180 |
181 | {record['displays']['mode']}
182 |
183 |
184 | {record['displays']['nijiMode']}
185 |
186 |
187 | {record['displays']['subscribePlan']}
188 |
189 |
190 | {record['displays']['billedWay']}
191 |
192 |
193 | {record['displays']['renewDate']}
194 |
195 |
196 | {record.fastTimeRemaining}
197 |
198 |
199 | {record.relaxedUsage}
200 |
201 |
202 | {record.fastUsage}
203 |
204 |
205 | {record.turboUsage}
206 |
207 |
208 | {record.lifetimeUsage}
209 |
210 |
211 |
212 |
213 |
218 |
219 |
220 | {record.coreSize}
221 |
222 |
223 | {record.queueSize}
224 |
225 |
226 | {record.timeoutMinutes} {intl.formatMessage({ id: 'pages.minutes' })}
227 |
228 |
229 | {record.weight}
230 |
231 |
232 | {changeDate(record.dateCreated)}
233 |
234 |
235 | {getTooltip(record.remark)}
236 |
237 |
238 | {getTooltip(record.disabledReason)}
239 |
240 |
241 |
242 |
243 |
248 |
255 | {versionSelectorOptions()}
256 |
257 |
258 | {accountButtons()}
259 |
260 |
261 |
266 |
267 | {accountNijiButtons()}
268 |
269 |
270 | >
271 | );
272 | };
273 |
274 | export default MoreContent;
275 |
--------------------------------------------------------------------------------
/src/pages/AccountList/components/contents/UpdateContent.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Alert,
3 | Button,
4 | Card,
5 | Col,
6 | Form,
7 | FormInstance,
8 | Input,
9 | InputNumber,
10 | Modal,
11 | Row,
12 | Select,
13 | Switch,
14 | } from 'antd';
15 | import { useEffect, useState } from 'react';
16 |
17 | import { allDomain } from '@/services/mj/api';
18 | import { FullscreenOutlined } from '@ant-design/icons';
19 | import { useIntl } from '@umijs/max';
20 |
21 | const UpdateContent = ({
22 | form,
23 | onSubmit,
24 | record,
25 | r,
26 | }: {
27 | form: FormInstance;
28 | onSubmit: (values: any) => void;
29 | record: Record;
30 | r: any;
31 | }) => {
32 | const intl = useIntl();
33 |
34 | // // 当组件挂载或者record更新时,设置表单的初始值
35 | // useEffect(() => {
36 | // form.setFieldsValue(record);
37 | // });
38 |
39 | useEffect(() => {
40 | form.setFieldsValue(record);
41 | }, []);
42 |
43 | useEffect(() => {
44 | form.setFieldsValue(record);
45 | }, [r]);
46 |
47 | const [opts, setOpts] = useState([]);
48 | useEffect(() => {
49 | allDomain().then((res) => {
50 | if (res.success) {
51 | setOpts(res.data);
52 | }
53 | });
54 | }, []);
55 | const [isModalVisible, setIsModalVisible] = useState(false);
56 | const [subChannels, setSubChannels] = useState('');
57 |
58 | const showModal = () => {
59 | setIsModalVisible(true);
60 | };
61 |
62 | const handleOk = () => {
63 | record.subChannels = subChannels.split('\n');
64 | form.setFieldsValue({ subChannels: subChannels.split('\n') });
65 | setIsModalVisible(false);
66 | };
67 |
68 | const handleCancel = () => {
69 | setIsModalVisible(false);
70 | };
71 |
72 | return (
73 |
249 | );
250 | };
251 |
252 | export default UpdateContent;
253 |
--------------------------------------------------------------------------------
/src/pages/AccountList/index.less:
--------------------------------------------------------------------------------
1 | .tableToolbar {
2 | text-align: right;
3 | padding: 15px 0;
4 | }
--------------------------------------------------------------------------------
/src/pages/BannedWordList/List/index.less:
--------------------------------------------------------------------------------
1 | .tableToolbar{
2 | text-align: right;
3 | padding: 15px 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/BannedWordList/List/index.tsx:
--------------------------------------------------------------------------------
1 | import MyModal from '@/pages/components/Modal';
2 | import AddContent from '@/pages/DomainList/components/AddContent';
3 | import { createIllegalWord, deleteIllegalWord, queryIllegalWord } from '@/services/mj/api';
4 | import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
5 | import { PageContainer, ProTable } from '@ant-design/pro-components';
6 | import { useIntl } from '@umijs/max';
7 | import { Button, Card, Form, notification, Popconfirm, Space, Switch } from 'antd';
8 | import React, { useRef, useState } from 'react';
9 |
10 | const UserList: React.FC = () => {
11 | // 初始化 dataSource 状态为空数组
12 | const [modalVisible, setModalVisible] = useState(false);
13 | const [modalContent, setModalContent] = useState({});
14 | const [title, setTitle] = useState('');
15 | const [footer, setFooter] = useState({});
16 | const [modalWidth, setModalWidth] = useState(1000);
17 | const [form] = Form.useForm();
18 | const intl = useIntl();
19 |
20 | const [api, contextHolder] = notification.useNotification();
21 |
22 | const actionRef = useRef();
23 |
24 | const hideModal = () => {
25 | setModalContent({});
26 | setFooter({});
27 | setModalVisible(false);
28 | };
29 | const [modalSubmitLoading, setModalSubmitLoading] = useState(false);
30 | const modalFooter = (
31 | <>
32 |
33 | {intl.formatMessage({ id: 'pages.cancel' })}
34 |
35 | form.submit()}
40 | >
41 | {intl.formatMessage({ id: 'pages.submit' })}
42 |
43 | >
44 | );
45 |
46 | const handleAdd = async (values: Record) => {
47 | setModalSubmitLoading(true);
48 |
49 | // 判断如果 keywords 为空,则设置为空数组
50 | if (!values.keywords) {
51 | values.keywords = [];
52 | }
53 |
54 | // 如果是字符串,则转换为数组
55 | if (typeof values.keywords === 'string') {
56 | values.keywords = values.keywords.split(',');
57 | }
58 |
59 | const res = await createIllegalWord(values);
60 | if (res.success) {
61 | api.success({
62 | message: 'success',
63 | description: res.message,
64 | });
65 | hideModal();
66 | actionRef.current?.reload();
67 | } else {
68 | api.error({
69 | message: 'error',
70 | description: res.message,
71 | });
72 | }
73 | setModalSubmitLoading(false);
74 | };
75 |
76 | const openModal = (title: string, content: any, footer: any, modalWidth: number) => {
77 | form.resetFields();
78 | setTitle(title);
79 | setModalContent(content);
80 | setFooter(footer);
81 | setModalWidth(modalWidth);
82 | setModalVisible(true);
83 | };
84 |
85 | const onDelete = async (id: string) => {
86 | try {
87 | const res = await deleteIllegalWord(id);
88 | if (res.success) {
89 | api.success({
90 | message: 'success',
91 | description: intl.formatMessage({ id: 'pages.task.deleteSuccess' }),
92 | });
93 | actionRef.current?.reload();
94 | } else {
95 | api.error({
96 | message: 'error',
97 | description: res.message,
98 | });
99 | }
100 | } catch (error) {
101 | console.error(error);
102 | } finally {
103 | }
104 | };
105 |
106 | const columns = [
107 | {
108 | title: intl.formatMessage({ id: 'pages.word.name' }),
109 | dataIndex: 'name',
110 | width: 160,
111 | align: 'left',
112 | fixed: 'left',
113 | },
114 | {
115 | title: intl.formatMessage({ id: 'pages.word.description' }),
116 | dataIndex: 'description',
117 | width: 160,
118 | align: 'left',
119 | },
120 | {
121 | title: intl.formatMessage({ id: 'pages.word.keywords' }),
122 | dataIndex: 'keywords',
123 | width: 120,
124 | align: 'left',
125 | // 弹出显示纯文本
126 | render: (text, record) => {
127 | return {record?.keywords.length || '0'} {intl.formatMessage({ id: 'pages.word.keywords' })} ;
128 | },
129 | },
130 | {
131 | title: intl.formatMessage({ id: 'pages.word.enable' }),
132 | dataIndex: 'enable',
133 | width: 80,
134 | showInfo: false,
135 | hideInSearch: true,
136 | align: 'left',
137 | render: (text, record) => {
138 | return ;
139 | },
140 | },
141 | {
142 | title: intl.formatMessage({ id: 'pages.word.weight' }),
143 | dataIndex: 'weight',
144 | width: 80,
145 | align: 'left',
146 | hideInSearch: true,
147 | },
148 | {
149 | title: intl.formatMessage({ id: 'pages.word.createTime' }),
150 | dataIndex: 'createTimeFormat',
151 | width: 140,
152 | ellipsis: true,
153 | hideInSearch: true,
154 | },
155 | {
156 | title: intl.formatMessage({ id: 'pages.word.updateTime' }),
157 | dataIndex: 'updateTimeFormat',
158 | width: 140,
159 | ellipsis: true,
160 | hideInSearch: true,
161 | },
162 | {
163 | title: intl.formatMessage({ id: 'pages.operation' }),
164 | dataIndex: 'operation',
165 | width: 140,
166 | key: 'operation',
167 | fixed: 'right',
168 | align: 'center',
169 | hideInSearch: true,
170 | render: (_, record) => {
171 | return (
172 |
173 | }
176 | onClick={() =>
177 | openModal(
178 | intl.formatMessage({ id: 'pages.word.update' }),
179 | ,
180 | modalFooter,
181 | 1000,
182 | )
183 | }
184 | />
185 | onDelete(record.id)}
189 | >
190 | }>
191 |
192 |
193 | );
194 | },
195 | },
196 | ];
197 |
198 | return (
199 |
200 | {contextHolder}
201 |
202 | [
214 | }
218 | onClick={() =>
219 | openModal(
220 | intl.formatMessage({ id: 'pages.word.add' }),
221 | ,
222 | modalFooter,
223 | 1000,
224 | )
225 | }
226 | >
227 | {intl.formatMessage({ id: 'pages.add' })}
228 | ,
229 | ]}
230 | request={async (params) => {
231 | // 如果 params.keywords 是 string 转为 array
232 | if (params.keywords) {
233 | if (typeof params.keywords === 'string') {
234 | params.keywords = params.keywords.split(',');
235 | }
236 | }
237 | const res = await queryIllegalWord({ ...params, pageNumber: params.current! - 1 });
238 | return {
239 | data: res.list,
240 | total: res.pagination.total,
241 | success: true,
242 | };
243 | }}
244 | />
245 |
246 |
254 |
255 | );
256 | };
257 |
258 | export default UserList;
259 |
--------------------------------------------------------------------------------
/src/pages/BannedWordList/components/AddContent.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Col, Form, FormInstance, Input, InputNumber, Row, Switch } from 'antd';
2 | import { useEffect, useState } from 'react';
3 |
4 | import { useIntl } from '@umijs/max';
5 |
6 | const UpdateContent = ({
7 | form,
8 | onSubmit,
9 | record,
10 | }: {
11 | form: FormInstance;
12 | onSubmit: (values: any) => void;
13 | record: Record;
14 | }) => {
15 | const intl = useIntl();
16 | const [disabledRole, setDisabledRole] = useState(false);
17 |
18 | // 当组件挂载或者record更新时,设置表单的初始值
19 | useEffect(() => {
20 | form.setFieldsValue(record);
21 |
22 | // 如果是管理员,则禁用角色选择
23 | if (record.id == 'admin') {
24 | setDisabledRole(true);
25 | } else {
26 | setDisabledRole(false);
27 | }
28 | console.log('record', record);
29 | });
30 |
31 | return (
32 |
77 | );
78 | };
79 |
80 | export default UpdateContent;
81 |
--------------------------------------------------------------------------------
/src/pages/DomainList/List/index.less:
--------------------------------------------------------------------------------
1 | .tableToolbar{
2 | text-align: right;
3 | padding: 15px 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/DomainList/List/index.tsx:
--------------------------------------------------------------------------------
1 | import MyModal from '@/pages/components/Modal';
2 | import AddContent from '@/pages/DomainList/components/AddContent';
3 | import { createDomain, deleteDomain, queryDomain } from '@/services/mj/api';
4 | import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
5 | import { PageContainer, ProTable } from '@ant-design/pro-components';
6 | import { useIntl } from '@umijs/max';
7 | import { Button, Card, Form, notification, Popconfirm, Space, Switch } from 'antd';
8 | import React, { useRef, useState } from 'react';
9 |
10 | const UserList: React.FC = () => {
11 | // 初始化 dataSource 状态为空数组
12 | const [modalVisible, setModalVisible] = useState(false);
13 | const [modalContent, setModalContent] = useState({});
14 | const [title, setTitle] = useState('');
15 | const [footer, setFooter] = useState({});
16 | const [modalWidth, setModalWidth] = useState(1000);
17 | const [form] = Form.useForm();
18 | const intl = useIntl();
19 |
20 | const [api, contextHolder] = notification.useNotification();
21 |
22 | const actionRef = useRef();
23 |
24 | const hideModal = () => {
25 | setModalContent({});
26 | setFooter({});
27 | setModalVisible(false);
28 | };
29 | const [modalSubmitLoading, setModalSubmitLoading] = useState(false);
30 | const modalFooter = (
31 | <>
32 |
33 | {intl.formatMessage({ id: 'pages.cancel' })}
34 |
35 | form.submit()}
40 | >
41 | {intl.formatMessage({ id: 'pages.submit' })}
42 |
43 | >
44 | );
45 |
46 | const handleAdd = async (values: Record) => {
47 | setModalSubmitLoading(true);
48 |
49 | // 判断如果 keywords 为空,则设置为空数组
50 | if (!values.keywords) {
51 | values.keywords = [];
52 | }
53 |
54 | // 如果是字符串,则转换为数组
55 | if (typeof values.keywords === 'string') {
56 | values.keywords = values.keywords.split(',');
57 | }
58 |
59 | const res = await createDomain(values);
60 | if (res.success) {
61 | api.success({
62 | message: 'success',
63 | description: res.message,
64 | });
65 | hideModal();
66 | actionRef.current?.reload();
67 | } else {
68 | api.error({
69 | message: 'error',
70 | description: res.message,
71 | });
72 | }
73 | setModalSubmitLoading(false);
74 | };
75 |
76 | const openModal = (title: string, content: any, footer: any, modalWidth: number) => {
77 | form.resetFields();
78 | setTitle(title);
79 | setModalContent(content);
80 | setFooter(footer);
81 | setModalWidth(modalWidth);
82 | setModalVisible(true);
83 | };
84 |
85 | const onDelete = async (id: string) => {
86 | try {
87 | const res = await deleteDomain(id);
88 | if (res.success) {
89 | api.success({
90 | message: 'success',
91 | description: intl.formatMessage({ id: 'pages.task.deleteSuccess' }),
92 | });
93 | actionRef.current?.reload();
94 | } else {
95 | api.error({
96 | message: 'error',
97 | description: res.message,
98 | });
99 | }
100 | } catch (error) {
101 | console.error(error);
102 | } finally {
103 | }
104 | };
105 |
106 | const columns = [
107 | {
108 | title: intl.formatMessage({ id: 'pages.domain.name' }),
109 | dataIndex: 'name',
110 | width: 160,
111 | align: 'left',
112 | fixed: 'left',
113 | },
114 | {
115 | title: intl.formatMessage({ id: 'pages.domain.description' }),
116 | dataIndex: 'description',
117 | width: 160,
118 | align: 'left',
119 | },
120 | {
121 | title: intl.formatMessage({ id: 'pages.domain.keywords' }),
122 | dataIndex: 'keywords',
123 | width: 120,
124 | align: 'left',
125 | // 弹出显示纯文本
126 | render: (text, record) => {
127 | return {record?.keywords.length || '0'} {intl.formatMessage({ id: 'pages.domain.keywords' })} ;
128 | },
129 | },
130 | {
131 | title: intl.formatMessage({ id: 'pages.domain.enable' }),
132 | dataIndex: 'enable',
133 | width: 80,
134 | showInfo: false,
135 | hideInSearch: true,
136 | align: 'left',
137 | render: (text, record) => {
138 | return ;
139 | },
140 | },
141 | {
142 | title: intl.formatMessage({ id: 'pages.domain.weight' }),
143 | dataIndex: 'weight',
144 | width: 80,
145 | align: 'left',
146 | hideInSearch: true,
147 | },
148 | {
149 | title: intl.formatMessage({ id: 'pages.domain.createTime' }),
150 | dataIndex: 'createTimeFormat',
151 | width: 140,
152 | ellipsis: true,
153 | hideInSearch: true,
154 | },
155 | {
156 | title: intl.formatMessage({ id: 'pages.domain.updateTime' }),
157 | dataIndex: 'updateTimeFormat',
158 | width: 140,
159 | ellipsis: true,
160 | hideInSearch: true,
161 | },
162 | {
163 | title: intl.formatMessage({ id: 'pages.operation' }),
164 | dataIndex: 'operation',
165 | width: 140,
166 | key: 'operation',
167 | fixed: 'right',
168 | align: 'center',
169 | hideInSearch: true,
170 | render: (_, record) => {
171 | return (
172 |
173 | }
176 | onClick={() =>
177 | openModal(
178 | intl.formatMessage({ id: 'pages.domain.update' }),
179 | ,
180 | modalFooter,
181 | 1000,
182 | )
183 | }
184 | />
185 | onDelete(record.id)}
189 | >
190 | }>
191 |
192 |
193 | );
194 | },
195 | },
196 | ];
197 |
198 | return (
199 |
200 | {contextHolder}
201 |
202 | [
214 | }
218 | onClick={() =>
219 | openModal(
220 | intl.formatMessage({ id: 'pages.domain.add' }),
221 | ,
222 | modalFooter,
223 | 1000,
224 | )
225 | }
226 | >
227 | {intl.formatMessage({ id: 'pages.add' })}
228 | ,
229 | ]}
230 | request={async (params) => {
231 | // 如果 params.keywords 是 string 转为 array
232 | if (params.keywords) {
233 | if (typeof params.keywords === 'string') {
234 | params.keywords = params.keywords.split(',');
235 | }
236 | }
237 | const res = await queryDomain({ ...params, pageNumber: params.current! - 1 });
238 | return {
239 | data: res.list,
240 | total: res.pagination.total,
241 | success: true,
242 | };
243 | }}
244 | />
245 |
246 |
254 |
255 | );
256 | };
257 |
258 | export default UserList;
259 |
--------------------------------------------------------------------------------
/src/pages/DomainList/components/AddContent.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Col, Form, FormInstance, Input, InputNumber, Row, Switch } from 'antd';
2 | import { useEffect, useState } from 'react';
3 |
4 | import { useIntl } from '@umijs/max';
5 |
6 | const UpdateContent = ({
7 | form,
8 | onSubmit,
9 | record,
10 | }: {
11 | form: FormInstance;
12 | onSubmit: (values: any) => void;
13 | record: Record;
14 | }) => {
15 | const intl = useIntl();
16 | const [disabledRole, setDisabledRole] = useState(false);
17 |
18 | // 当组件挂载或者record更新时,设置表单的初始值
19 | useEffect(() => {
20 | form.setFieldsValue(record);
21 |
22 | // 如果是管理员,则禁用角色选择
23 | if (record.id == 'admin') {
24 | setDisabledRole(true);
25 | } else {
26 | setDisabledRole(false);
27 | }
28 | console.log('record', record);
29 | });
30 |
31 | return (
32 |
77 | );
78 | };
79 |
80 | export default UpdateContent;
81 |
--------------------------------------------------------------------------------
/src/pages/Draw/index.less:
--------------------------------------------------------------------------------
1 | .cardTitleTime {
2 | font-size: 13px;
3 | font-weight: normal;
4 | margin-left: 10px;
5 | color: gray;
6 | }
7 |
8 | .taskErrorTip {
9 | color: red;
10 | font-size: 15px;
11 | }
12 |
--------------------------------------------------------------------------------
/src/pages/Probe/index.less:
--------------------------------------------------------------------------------
1 | // index.less
2 | .logCard {
3 | border-radius: 4px; // 设置边框圆角
4 | padding: 16px; // 设置内边距
5 | height: 75vh; // 设置卡片高度
6 | overflow-y: auto; // 设置垂直滚动
7 | }
8 |
9 | .logContent {
10 | white-space: pre-wrap; // 保留空白符和换行符
11 | font-family: monospace; // 设置字体为等宽字体
12 | font-size: 14px; // 设置字体大小
13 | color: #333; // 设置字体颜色
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/Probe/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from 'react';
2 | import { Card, Button, Select, Space } from 'antd';
3 | import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons';
4 | import { PageContainer } from '@ant-design/pro-components';
5 | import { probe } from "@/services/mj/api";
6 | import styles from './index.less';
7 | import { useIntl } from '@umijs/max';
8 |
9 | const { Option } = Select;
10 |
11 |
12 | const Probe: React.FC = () => {
13 | const [logContent, setLogContent] = useState('');
14 | const [refreshInterval, setRefreshInterval] = useState(5);
15 | const logCardRef = useRef(null);
16 | const intervalIdRef = useRef(null);
17 | const intl = useIntl();
18 | const [level, setLevel] = useState(0);
19 |
20 | const fetchLogContent = async (l = 0) => {
21 | return await probe(1000, l);
22 | };
23 |
24 | useEffect(() => {
25 | const tailLog = async () => {
26 | const res = await fetchLogContent(level);
27 | const logCardElement = logCardRef.current || document.body;
28 | const scrollN = logCardElement.scrollHeight - logCardElement.scrollTop - logCardElement.clientHeight;
29 | setLogContent(res);
30 | if (scrollN < 50) {
31 | logCardElement.scrollTop = logCardElement.scrollHeight - logCardElement.clientHeight;
32 | }
33 | };
34 |
35 | tailLog().then(() => {
36 | setTimeout(() => {
37 | const logCardElement = logCardRef.current || document.body;
38 | logCardElement.scrollTop = logCardElement.scrollHeight - logCardElement.clientHeight;
39 | }, 100);
40 | });
41 |
42 | if (intervalIdRef.current) {
43 | clearInterval(intervalIdRef.current);
44 | }
45 |
46 | if (refreshInterval > 0) {
47 | const newIntervalId = setInterval(tailLog, refreshInterval * 1000);
48 | intervalIdRef.current = newIntervalId;
49 | }
50 |
51 | return () => {
52 | if (intervalIdRef.current) {
53 | clearInterval(intervalIdRef.current);
54 | intervalIdRef.current = null;
55 | }
56 | };
57 | }, [refreshInterval, level]);
58 |
59 | const handleRefreshChange = (value: number) => {
60 | setRefreshInterval(value);
61 | };
62 |
63 | const handleLevelChange = (value: number) => {
64 | setLevel(value)
65 | };
66 |
67 | const toggleFullscreen = () => {
68 | if (logCardRef.current) {
69 | if (document.fullscreenElement) {
70 | document.exitFullscreen();
71 | } else {
72 | logCardRef.current.requestFullscreen();
73 | }
74 | }
75 | };
76 |
77 | return (
78 |
79 |
80 |
81 | {document.fullscreenElement ? : }
82 | {document.fullscreenElement ? intl.formatMessage({ id: 'pages.probe.fullscreenExit' }) : intl.formatMessage({ id: 'pages.probe.fullscreen' })}
83 |
84 |
85 | 5 {intl.formatMessage({ id: 'pages.seconds' })}
86 | 10 {intl.formatMessage({ id: 'pages.seconds' })}
87 | 30 {intl.formatMessage({ id: 'pages.seconds' })}
88 | {intl.formatMessage({ id: 'pages.probe.stopRefresh' })}
89 |
90 |
91 | Info
92 | Error
93 |
94 |
95 |
96 |
99 |
100 |
101 | );
102 | };
103 |
104 | export default Probe;
105 |
--------------------------------------------------------------------------------
/src/pages/Setting/index.less:
--------------------------------------------------------------------------------
1 | // index.less
2 |
--------------------------------------------------------------------------------
/src/pages/Task/List/index.less:
--------------------------------------------------------------------------------
1 | .tableToolbar{
2 | text-align: right;
3 | padding: 15px 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/Task/List/index.tsx:
--------------------------------------------------------------------------------
1 | import MyModal from '@/pages/components/Modal';
2 | import TaskContent from '@/pages/Task/components/TaskContent';
3 | import { deleteTask, queryTask } from '@/services/mj/api';
4 | import { DeleteOutlined } from '@ant-design/icons';
5 | import { PageContainer, ProTable } from '@ant-design/pro-components';
6 | import { useIntl } from '@umijs/max';
7 | import { Button, Card, Form, Image, notification, Popconfirm, Progress, Spin, Tag } from 'antd';
8 | import React, { useRef, useState } from 'react';
9 |
10 | const List: React.FC = () => {
11 | // 初始化 dataSource 状态为空数组
12 | const [modalVisible, setModalVisible] = useState(false);
13 | const [modalContent, setModalContent] = useState({});
14 | const [title, setTitle] = useState('');
15 | const [footer, setFooter] = useState({});
16 | const [modalWidth, setModalWidth] = useState(1000);
17 | const [form] = Form.useForm();
18 | const intl = useIntl();
19 |
20 | const imagePrefix = sessionStorage.getItem('mj-image-prefix') || '';
21 |
22 | const [api, contextHolder] = notification.useNotification();
23 |
24 | const actionRef = useRef();
25 |
26 | const hideModal = () => {
27 | setModalContent({});
28 | setFooter({});
29 | setModalVisible(false);
30 | };
31 |
32 | const openModal = (title: string, content: any, footer: any, modalWidth: number) => {
33 | form.resetFields();
34 | setTitle(title);
35 | setModalContent(content);
36 | setFooter(footer);
37 | setModalWidth(modalWidth);
38 | setModalVisible(true);
39 | };
40 |
41 | const onDelete = async (id: string) => {
42 | try {
43 | const res = await deleteTask(id);
44 | if (res.success) {
45 | api.success({
46 | message: 'success',
47 | description: intl.formatMessage({ id: 'pages.task.deleteSuccess' }),
48 | });
49 | actionRef.current?.reload();
50 | } else {
51 | api.error({
52 | message: 'error',
53 | description: res.message,
54 | });
55 | }
56 | } catch (error) {
57 | console.error(error);
58 | } finally {
59 | }
60 | };
61 |
62 | const [loading, setLoading] = useState(true);
63 |
64 | const handleImageLoad = () => {
65 | setLoading(false);
66 | };
67 |
68 | // get Video
69 | const getVideo = (url: string) => {
70 | if (!url) return url;
71 | return (
72 | }
77 | >
78 | );
79 | };
80 |
81 | const columns = [
82 | {
83 | title: 'ID',
84 | dataIndex: 'id',
85 | width: 160,
86 | align: 'center',
87 | fixed: 'left',
88 | render: (text, record) => (
89 |
91 | openModal(
92 | intl.formatMessage({ id: 'pages.task.info' }),
93 | ,
94 | null,
95 | 1100,
96 | )
97 | }
98 | >
99 | {text}
100 |
101 | ),
102 | },
103 | {
104 | title: intl.formatMessage({ id: 'pages.task.type' }),
105 | dataIndex: 'action',
106 | width: 140,
107 | align: 'center',
108 | request: async () => [
109 | {
110 | label: 'Imagine',
111 | value: 'IMAGINE',
112 | },
113 | {
114 | label: 'Upscale',
115 | value: 'UPSCALE',
116 | },
117 | {
118 | label: 'Variation',
119 | value: 'VARIATION',
120 | },
121 | {
122 | label: 'Zoom',
123 | value: 'ZOOM',
124 | },
125 | {
126 | label: 'Pan',
127 | value: 'PAN',
128 | },
129 | {
130 | label: 'Describe',
131 | value: 'DESCRIBE',
132 | },
133 | {
134 | label: 'Blend',
135 | value: 'BLEND',
136 | },
137 | {
138 | label: 'Shorten',
139 | value: 'SHORTEN',
140 | },
141 | {
142 | label: 'SwapFace',
143 | value: 'SWAP_FACE',
144 | },
145 | {
146 | label: 'SwapVideoFace',
147 | value: 'SWAP_VIDEO_FACE',
148 | },
149 | ],
150 | render: (text, record) => record['displays']['action'],
151 | },
152 | {
153 | title: intl.formatMessage({ id: 'pages.task.preview' }),
154 | dataIndex: 'imageUrl',
155 | width: 80,
156 | align: 'center',
157 | hideInSearch: true,
158 | render: (text, record, index) => {
159 | if (record.action === 'SWAP_VIDEO_FACE') {
160 | // 点击图片显示视频
161 | return (
162 | record.thumbnailUrl && (
163 | {
170 | window.open(record.imageUrl);
171 | }}
172 | loading="lazy"
173 | onLoad={handleImageLoad}
174 | placeholder={
175 |
184 |
185 |
186 | }
187 | />
188 | )
189 | );
190 | }
191 |
192 | return (
193 | (record.thumbnailUrl || record.imageUrl) && (
194 |
195 | ,
203 | }}
204 | loading="lazy"
205 | onLoad={handleImageLoad}
206 | placeholder={
207 |
216 |
217 |
218 | }
219 | />
220 |
221 | )
222 | );
223 | },
224 | },
225 | {
226 | title: intl.formatMessage({ id: 'pages.task.instanceId' }),
227 | dataIndex: 'instanceId',
228 | width: 180,
229 | align: 'center',
230 | render: (text, record) => record['properties']['discordInstanceId'],
231 | },
232 | {
233 | title: intl.formatMessage({ id: 'pages.task.submitTime' }),
234 | dataIndex: 'submitTime',
235 | width: 160,
236 | hideInSearch: true,
237 | align: 'center',
238 | render: (text, record) => record['displays']['submitTime'],
239 | },
240 |
241 | {
242 | title: intl.formatMessage({ id: 'pages.task.status' }),
243 | dataIndex: 'status',
244 | width: 90,
245 | align: 'center',
246 | request: async () => [
247 | {
248 | label: intl.formatMessage({ id: 'pages.task.NOT_START' }),
249 | value: 'NOT_START',
250 | },
251 | {
252 | label: intl.formatMessage({ id: 'pages.task.SUBMITTED' }),
253 | value: 'SUBMITTED',
254 | },
255 | {
256 | label: intl.formatMessage({ id: 'pages.task.MODAL' }),
257 | value: 'MODAL',
258 | },
259 | {
260 | label: intl.formatMessage({ id: 'pages.task.IN_PROGRESS' }),
261 | value: 'IN_PROGRESS',
262 | },
263 | {
264 | label: intl.formatMessage({ id: 'pages.task.FAILURE' }),
265 | value: 'FAILURE',
266 | },
267 | {
268 | label: intl.formatMessage({ id: 'pages.task.SUCCESS' }),
269 | value: 'SUCCESS',
270 | },
271 | {
272 | label: intl.formatMessage({ id: 'pages.task.CANCEL' }),
273 | value: 'CANCEL',
274 | },
275 | ],
276 | render: (text, record) => {
277 | let color = 'default';
278 | if (text == 'NOT_START') {
279 | color = 'default';
280 | } else if (text == 'SUBMITTED') {
281 | color = 'lime';
282 | } else if (text == 'MODAL') {
283 | color = 'warning';
284 | } else if (text == 'IN_PROGRESS') {
285 | color = 'processing';
286 | } else if (text == 'FAILURE') {
287 | color = 'error';
288 | } else if (text == 'SUCCESS') {
289 | color = 'success';
290 | } else if (text == 'CANCEL') {
291 | color = 'magenta';
292 | }
293 | return {record['displays']['status']} ;
294 | },
295 | },
296 | {
297 | title: intl.formatMessage({ id: 'pages.task.progress' }),
298 | dataIndex: 'progress',
299 | width: 130,
300 | showInfo: false,
301 | hideInSearch: true,
302 | render: (text, record) => {
303 | let percent = 0;
304 | if (text && text.indexOf('%') > 0) {
305 | percent = parseInt(text.substring(0, text.indexOf('%')));
306 | }
307 | let status = 'normal';
308 | if (record['status'] == 'SUCCESS') {
309 | status = 'success';
310 | } else if (record['status'] == 'FAILURE') {
311 | status = 'exception';
312 | }
313 | return ;
314 | },
315 | },
316 | {
317 | title: intl.formatMessage({ id: 'pages.task.description' }),
318 | dataIndex: 'description',
319 | width: 250,
320 | ellipsis: true,
321 | },
322 | {
323 | title: intl.formatMessage({ id: 'pages.task.failReason' }),
324 | dataIndex: 'failReason',
325 | width: 220,
326 | ellipsis: true,
327 | },
328 | {
329 | title: intl.formatMessage({ id: 'pages.operation' }),
330 | dataIndex: 'operation',
331 | width: 100,
332 | key: 'operation',
333 | fixed: 'right',
334 | align: 'center',
335 | hideInSearch: true,
336 | render: (_, record) => {
337 | return (
338 | onDelete(record.id)}
342 | >
343 | }>
344 |
345 | );
346 | },
347 | },
348 | ];
349 |
350 | return (
351 |
352 | {contextHolder}
353 |
354 | {
366 | const res = await queryTask({ ...params, pageNumber: params.current - 1 });
367 | const images = res.list.map((item) => item.imageUrl || '').filter((item) => item != '');
368 | const list = res.list;
369 | list.forEach((item, index) => {
370 | item.images = images;
371 | });
372 | return {
373 | data: list,
374 | total: res.pagination.total,
375 | success: true,
376 | };
377 | }}
378 | />
379 |
380 |
388 |
389 | );
390 | };
391 |
392 | export default List;
393 |
--------------------------------------------------------------------------------
/src/pages/Task/components/TaskContent.tsx:
--------------------------------------------------------------------------------
1 | import { useIntl } from '@umijs/max';
2 | import { Card, Descriptions, Flex, Image, Progress, Spin, Tag, Tooltip } from 'antd';
3 |
4 | const TaskContent = ({ record }: { record: Record }) => {
5 | const intl = useIntl();
6 | const imagePrefix = sessionStorage.getItem('mj-image-prefix') || '';
7 |
8 | const getStatusTag = (text: string) => {
9 | let color = 'default';
10 | if (text == 'NOT_START') {
11 | color = 'default';
12 | } else if (text == 'SUBMITTED') {
13 | color = 'lime';
14 | } else if (text == 'MODAL') {
15 | color = 'warning';
16 | } else if (text == 'IN_PROGRESS') {
17 | color = 'processing';
18 | } else if (text == 'FAILURE') {
19 | color = 'error';
20 | } else if (text == 'SUCCESS') {
21 | color = 'success';
22 | } else if (text == 'CANCEL') {
23 | color = 'magenta';
24 | }
25 | return {record['displays']['status']} ;
26 | };
27 |
28 | const getProgress = (text: string) => {
29 | let percent = 0;
30 | if (text && text.indexOf('%') > 0) {
31 | percent = parseInt(text.substring(0, text.indexOf('%')));
32 | }
33 | let status = 'normal';
34 | if (record['status'] == 'SUCCESS') {
35 | status = 'success';
36 | } else if (record['status'] == 'FAILURE') {
37 | status = 'exception';
38 | }
39 | return (
40 |
43 | );
44 | };
45 |
46 | const getTooltip = (text: string) => {
47 | if (!text || text.length < 30) return text;
48 | return {text.substring(0, 30) + '...'} ;
49 | };
50 |
51 | const getImage = (url: string) => {
52 | if (!url) return url;
53 | return (
54 | }
58 | />
59 | );
60 | };
61 |
62 | // get Video
63 | const getVideo = (url: string) => {
64 | if (!url) return url;
65 | return (
66 | }
71 | >
72 | );
73 | };
74 |
75 | const getBotTypeTag = (botType: string) => {
76 | if (botType == 'NIJI_JOURNEY') {
77 | return niji・journey ;
78 | } else if (
79 | botType == 'INSIGHT_FACE' ||
80 | botType == 'FACE_SWAP' ||
81 | botType == 'FACE_SWAP_VIDEO'
82 | ) {
83 | return InsightFace ;
84 | } else if (botType == 'MID_JOURNEY') {
85 | return Midjourney ;
86 | } else {
87 | return '-';
88 | }
89 | };
90 |
91 | const getModalTag = (enable: boolean) => {
92 | if (enable == null || !enable) return;
93 | return {intl.formatMessage({ id: 'pages.yes' })} ;
94 | };
95 |
96 | return (
97 | <>
98 |
103 |
104 | {record.id}
105 |
106 | {record['displays']['action']}
107 |
108 |
109 | {getStatusTag(record.status)}
110 |
111 |
112 | {getProgress(record.progress)}
113 |
114 |
115 | {getTooltip(record.prompt)}
116 |
117 |
118 | {getTooltip(record.promptEn)}
119 |
120 |
121 | {getTooltip(record.description)}
122 |
123 |
124 | {record['displays']['submitTime']}
125 |
126 |
127 | {record['displays']['startTime']}
128 |
129 |
130 | {record['displays']['finishTime']}
131 |
132 |
133 | {getTooltip(record.failReason)}
134 |
135 |
136 | {getTooltip(record.state)}
137 |
138 |
139 | {record.action === 'SWAP_VIDEO_FACE' ? (
140 |
141 |
142 | {/*
143 | {getImage(record.replicateSource)}
144 | {getVideo(record.replicateTarget)}
145 | */}
146 | {getVideo(record.imageUrl)}
147 |
148 |
149 | ) : (
150 |
151 | {getImage(record.imageUrl)}
152 |
153 | )}
154 |
155 |
156 |
161 |
162 |
163 | {getBotTypeTag(record.botType)}
164 |
165 | {record.nonce}
166 |
167 | {record['properties']['discordInstanceId']}
168 |
169 |
170 | {record['properties']['discordInstanceId']}
171 |
172 | {record['properties']['messageHash']}
173 |
174 | {getTooltip(record['properties']['messageContent'])}
175 |
176 |
177 | {getTooltip(record['properties']['finalPrompt'])}
178 |
179 |
180 | {getTooltip(record.promptCn || '-')}
181 |
182 |
183 | {getTooltip(record['properties']['custom_id'] || '-')}
184 |
185 |
186 | {getModalTag(record['properties']['needModel']) || '-'}
187 |
188 |
189 | {record.seed || '-'}
190 |
191 |
192 | {getTooltip(record['properties']['notifyHook'] || '-')}
193 |
194 |
195 | {record.clientIp || '-'}
196 |
197 |
198 |
199 | >
200 | );
201 | };
202 |
203 | export default TaskContent;
204 |
--------------------------------------------------------------------------------
/src/pages/User/Login/index.tsx:
--------------------------------------------------------------------------------
1 | import { getIndex, login, register } from '@/services/mj/api';
2 | import { LockOutlined, MailOutlined } from '@ant-design/icons';
3 | import { LoginForm, ProFormText } from '@ant-design/pro-components';
4 | import { useEmotionCss } from '@ant-design/use-emotion-css';
5 | import { Helmet, SelectLang, useIntl, useModel } from '@umijs/max';
6 | import { Button, Input, message, Space } from 'antd';
7 | import React, { useEffect, useState } from 'react';
8 | import { flushSync } from 'react-dom';
9 | import Settings from '../../../../config/defaultSettings';
10 |
11 | const Lang = () => {
12 | const langClassName = useEmotionCss(({ token }) => {
13 | return {
14 | width: 42,
15 | height: 42,
16 | lineHeight: '42px',
17 | position: 'fixed',
18 | right: 16,
19 | borderRadius: token.borderRadius,
20 | ':hover': {
21 | backgroundColor: token.colorBgTextHover,
22 | },
23 | };
24 | });
25 |
26 | return (
27 |
28 | {SelectLang && }
29 |
30 | );
31 | };
32 |
33 | const Login: React.FC = () => {
34 | const { initialState, setInitialState } = useModel('@@initialState');
35 |
36 | const intl = useIntl();
37 |
38 | const [isRegister, setIsRegister] = useState(false);
39 | const [mail, setMail] = useState();
40 | const [registering, setRegistering] = useState(false);
41 |
42 | const containerClassName = useEmotionCss(() => {
43 | return {
44 | display: 'flex',
45 | flexDirection: 'column',
46 | height: '100vh',
47 | overflow: 'auto',
48 | backgroundImage:
49 | "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
50 | backgroundSize: '100% 100%',
51 | };
52 | });
53 |
54 | // register
55 | const onRegister = () => {
56 | if (!mail) {
57 | message.error('Please input email');
58 | return;
59 | }
60 |
61 | setRegistering(true);
62 | register(JSON.stringify({ email: mail })).then((res) => {
63 | setRegistering(false);
64 | if (res.success) {
65 | message.success(
66 | intl.formatMessage({
67 | id: 'pages.login.registerSuccess',
68 | }),
69 | );
70 | setIsRegister(false);
71 | } else {
72 | message.error(res.message);
73 | }
74 | });
75 | };
76 |
77 | const handleSubmit = async (values: API.LoginParams) => {
78 | try {
79 | // 登录
80 | const msg = await login(JSON.stringify(values.password || ''));
81 | if (msg.code === 1) {
82 | if (msg && msg.apiSecret) {
83 | sessionStorage.setItem('mj-api-secret', msg.apiSecret);
84 | }
85 |
86 | message.success(
87 | intl.formatMessage({
88 | id: 'pages.login.success',
89 | }),
90 | );
91 |
92 | const userInfo = await initialState?.fetchUserInfo?.();
93 | if (userInfo) {
94 | flushSync(() => {
95 | setInitialState((s) => ({
96 | ...s,
97 | currentUser: userInfo,
98 | }));
99 | });
100 | location.hash = '#/welcome';
101 | }
102 | return;
103 | }
104 | message.error(msg.description);
105 | } catch (error) {
106 | const defaultLoginFailureMessage = intl.formatMessage({
107 | id: 'pages.login.failure',
108 | });
109 | message.error(defaultLoginFailureMessage);
110 | }
111 | };
112 |
113 | // 是否显示注册
114 | const [showRegister, setShowRegister] = useState(false);
115 |
116 | useEffect(() => {
117 | getIndex().then((res) => {
118 | if (res.success) {
119 | if (res.data) {
120 | setShowRegister(res.data.isRegister);
121 | }
122 | }
123 | });
124 | }, []);
125 |
126 | return (
127 |
128 |
129 |
130 | {intl.formatMessage({ id: 'menu.login' })} - {Settings.title}
131 |
132 |
133 |
134 |
140 | {isRegister && showRegister ? (
141 |
151 | }
155 | placeholder={intl.formatMessage({
156 | id: 'pages.register.email.placeholder',
157 | defaultMessage: 'Email',
158 | })}
159 | value={mail}
160 | onChange={(e) => {
161 | setMail(e.target.value);
162 | }}
163 | />
164 |
165 |
172 | {intl.formatMessage({ id: 'pages.login.register' })}
173 |
174 | {
177 | setIsRegister(false);
178 | }}
179 | >
180 | {intl.formatMessage({ id: 'pages.login.returnLogin' })}
181 |
182 |
183 |
184 | ) : (
185 |
{
197 | await handleSubmit(values as API.LoginParams);
198 | }}
199 | actions={
200 | showRegister && (
201 | {
204 | setIsRegister(true);
205 | }}
206 | style={{ paddingLeft: 0 }}
207 | >
208 | {intl.formatMessage({ id: 'pages.login.registerAccount' })}
209 |
210 | )
211 | }
212 | >
213 | ,
218 | }}
219 | placeholder={intl.formatMessage({
220 | id: 'pages.login.password.placeholder',
221 | defaultMessage: 'Admin Token',
222 | })}
223 | />
224 |
225 | )}
226 |
227 |
228 | );
229 | };
230 |
231 | export default Login;
232 |
--------------------------------------------------------------------------------
/src/pages/UserList/List/index.less:
--------------------------------------------------------------------------------
1 | .tableToolbar{
2 | text-align: right;
3 | padding: 15px 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/pages/UserList/List/index.tsx:
--------------------------------------------------------------------------------
1 | import MyModal from '@/pages/components/Modal';
2 | import AddContent from '@/pages/UserList/components/AddContent';
3 | import { createUser, deleteUser, queryUser } from '@/services/mj/api';
4 | import { DeleteOutlined, EditOutlined, UserAddOutlined } from '@ant-design/icons';
5 | import { PageContainer, ProTable } from '@ant-design/pro-components';
6 | import { useIntl } from '@umijs/max';
7 | import { Button, Card, Checkbox, Form, notification, Popconfirm, Space, Tag } from 'antd';
8 | import moment from 'moment';
9 | import React, { useRef, useState } from 'react';
10 |
11 | const UserList: React.FC = () => {
12 | // 初始化 dataSource 状态为空数组
13 | const [modalVisible, setModalVisible] = useState(false);
14 | const [modalContent, setModalContent] = useState({});
15 | const [title, setTitle] = useState('');
16 | const [footer, setFooter] = useState({});
17 | const [modalWidth, setModalWidth] = useState(1000);
18 | const [form] = Form.useForm();
19 | const intl = useIntl();
20 |
21 | const [api, contextHolder] = notification.useNotification();
22 |
23 | const actionRef = useRef();
24 |
25 | const hideModal = () => {
26 | setModalContent({});
27 | setFooter({});
28 | setModalVisible(false);
29 | };
30 | const [modalSubmitLoading, setModalSubmitLoading] = useState(false);
31 | const modalFooter = (
32 | <>
33 |
34 | {intl.formatMessage({ id: 'pages.cancel' })}
35 |
36 | form.submit()}
41 | >
42 | {intl.formatMessage({ id: 'pages.submit' })}
43 |
44 | >
45 | );
46 |
47 | const handleAdd = async (values: Record) => {
48 | setModalSubmitLoading(true);
49 |
50 | // datepicker 传递的是 moment 对象,需要转换为字符串
51 | if (values.validStartTime) {
52 | values.validStartTime = moment(values.validStartTime).format('YYYY-MM-DD');
53 | }
54 | if (values.validEndTime) {
55 | values.validEndTime = moment(values.validEndTime).format('YYYY-MM-DD');
56 | }
57 |
58 | const res = await createUser(values);
59 | if (res.success) {
60 | api.success({
61 | message: 'success',
62 | description: res.message,
63 | });
64 | hideModal();
65 | actionRef.current?.reload();
66 | } else {
67 | api.error({
68 | message: 'error',
69 | description: res.message,
70 | });
71 | }
72 | setModalSubmitLoading(false);
73 | };
74 |
75 | const openModal = (title: string, content: any, footer: any, modalWidth: number) => {
76 | form.resetFields();
77 | setTitle(title);
78 | setModalContent(content);
79 | setFooter(footer);
80 | setModalWidth(modalWidth);
81 | setModalVisible(true);
82 | };
83 |
84 | const onDelete = async (id: string) => {
85 | try {
86 | const res = await deleteUser(id);
87 | if (res.success) {
88 | api.success({
89 | message: 'success',
90 | description: intl.formatMessage({ id: 'pages.task.deleteSuccess' }),
91 | });
92 | actionRef.current?.reload();
93 | } else {
94 | api.error({
95 | message: 'error',
96 | description: res.message,
97 | });
98 | }
99 | } catch (error) {
100 | console.error(error);
101 | } finally {
102 | }
103 | };
104 |
105 | const columns = [
106 | {
107 | title: intl.formatMessage({ id: 'pages.user.name' }),
108 | dataIndex: 'name',
109 | width: 140,
110 | align: 'center',
111 | fixed: 'left',
112 | },
113 | {
114 | title: intl.formatMessage({ id: 'pages.user.email' }),
115 | dataIndex: 'email',
116 | width: 140,
117 | align: 'center',
118 | },
119 | {
120 | title: intl.formatMessage({ id: 'pages.user.isWhite' }),
121 | dataIndex: 'isWhite',
122 | width: 90,
123 | align: 'center',
124 | render: (text) => {
125 | return ;
126 | },
127 | },
128 | {
129 | title: intl.formatMessage({ id: 'pages.user.status' }),
130 | dataIndex: 'status',
131 | width: 90,
132 | align: 'center',
133 | request: async () => [
134 | {
135 | label: intl.formatMessage({ id: 'pages.user.normal' }),
136 | value: 'NORMAL',
137 | },
138 | {
139 | label: intl.formatMessage({ id: 'pages.user.disabled' }),
140 | value: 'DISABLED',
141 | },
142 | ],
143 | render: (text, record) => {
144 | let color = 'default';
145 | if (text == 'NORMAL') {
146 | color = 'default';
147 | } else if (text == 'DISABLED') {
148 | color = 'error';
149 | }
150 | return {record.status} ;
151 | },
152 | },
153 | {
154 | title: intl.formatMessage({ id: 'pages.user.role' }),
155 | dataIndex: 'role',
156 | width: 140,
157 | showInfo: false,
158 | align: 'center',
159 | request: async () => [
160 | {
161 | label: intl.formatMessage({ id: 'pages.user.user' }),
162 | value: 'USER',
163 | },
164 | {
165 | label: intl.formatMessage({ id: 'pages.user.admin' }),
166 | value: 'ADMIN',
167 | },
168 | ],
169 | render: (text, record) => {
170 | let color = 'default';
171 | if (text == 'USER') {
172 | color = 'default';
173 | } else if (text == 'ADMIN') {
174 | color = 'success';
175 | }
176 | return {record.role} ;
177 | },
178 | },
179 | {
180 | title: intl.formatMessage({ id: 'pages.user.dayDrawLimit' }),
181 | dataIndex: 'dayDrawLimit',
182 | width: 140,
183 | align: 'center',
184 | hideInSearch: true,
185 | render: (text, record) => {
186 | if (text <= 0) {
187 | return intl.formatMessage({ id: 'pages.user.unlimited' });
188 | } else {
189 | return text;
190 | }
191 | },
192 | },
193 | {
194 | title: intl.formatMessage({ id: 'pages.user.totalDrawLimit' }),
195 | dataIndex: 'totalDrawLimit',
196 | width: 140,
197 | align: 'center',
198 | hideInSearch: true,
199 | render: (text, record) => {
200 | if (text <= 0) {
201 | return intl.formatMessage({ id: 'pages.user.unlimited' });
202 | } else {
203 | return text;
204 | }
205 | },
206 | },
207 | {
208 | title: intl.formatMessage({ id: 'pages.user.dayDrawCount' }),
209 | dataIndex: 'dayDrawCount',
210 | width: 140,
211 | ellipsis: true,
212 | hideInSearch: true,
213 | },
214 | {
215 | title: intl.formatMessage({ id: 'pages.user.totalDrawCount' }),
216 | dataIndex: 'totalDrawCount',
217 | width: 140,
218 | ellipsis: true,
219 | hideInSearch: true,
220 | },
221 | // {
222 | // title: intl.formatMessage({ id: 'pages.user.lastLoginIp' }),
223 | // dataIndex: 'lastLoginIp',
224 | // width: 140,
225 | // ellipsis: true,
226 | // hideInSearch: true,
227 | // },
228 | // {
229 | // title: intl.formatMessage({ id: 'pages.user.lastLoginTime' }),
230 | // dataIndex: 'lastLoginTimeFormat',
231 | // width: 140,
232 | // ellipsis: true,
233 | // hideInSearch: true,
234 | // },
235 | // {
236 | // title: intl.formatMessage({ id: 'pages.user.registerIp' }),
237 | // dataIndex: 'registerIp',
238 | // width: 140,
239 | // ellipsis: true,
240 | // hideInSearch: true,
241 | // },
242 | // {
243 | // title: intl.formatMessage({ id: 'pages.user.registerTime' }),
244 | // dataIndex: 'registerTimeFormat',
245 | // width: 140,
246 | // ellipsis: true,
247 | // hideInSearch: true,
248 | // },
249 | {
250 | title: intl.formatMessage({ id: 'pages.operation' }),
251 | dataIndex: 'operation',
252 | width: 140,
253 | key: 'operation',
254 | fixed: 'right',
255 | align: 'center',
256 | hideInSearch: true,
257 | render: (_, record: any) => {
258 | if (record.validStartTime) {
259 | record.validStartTime = moment(record.validStartTimeFormat, 'YYYY-MM-DD');
260 | }
261 | if (record.validEndTime) {
262 | record.validEndTime = moment(record.validEndTimeFormat, 'YYYY-MM-DD');
263 | }
264 |
265 | return (
266 |
267 | }
270 | onClick={() =>
271 | openModal(
272 | intl.formatMessage({ id: 'pages.user.update' }),
273 | ,
274 | modalFooter,
275 | 1000,
276 | )
277 | }
278 | />
279 | onDelete(record.id)}
283 | >
284 | }>
285 |
286 |
287 | );
288 | },
289 | },
290 | ];
291 |
292 | return (
293 |
294 | {contextHolder}
295 |
296 | [
308 | }
312 | onClick={() =>
313 | openModal(
314 | intl.formatMessage({ id: 'pages.user.add' }),
315 | ,
316 | modalFooter,
317 | 1000,
318 | )
319 | }
320 | >
321 | {intl.formatMessage({ id: 'pages.add' })}
322 | ,
323 | ]}
324 | request={async (params) => {
325 | const res = await queryUser({ ...params, pageNumber: params.current! - 1 });
326 | return {
327 | data: res.list,
328 | total: res.pagination.total,
329 | success: true,
330 | };
331 | }}
332 | />
333 |
334 |
342 |
343 | );
344 | };
345 |
346 | export default UserList;
347 |
--------------------------------------------------------------------------------
/src/pages/UserList/components/AddContent.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Card,
3 | Col,
4 | DatePicker,
5 | Form,
6 | FormInstance,
7 | Input,
8 | InputNumber,
9 | Row,
10 | Select,
11 | Switch,
12 | } from 'antd';
13 | import { useEffect, useState } from 'react';
14 |
15 | import { useIntl } from '@umijs/max';
16 |
17 | const UpdateContent = ({
18 | form,
19 | onSubmit,
20 | record,
21 | }: {
22 | form: FormInstance;
23 | onSubmit: (values: any) => void;
24 | record: Record;
25 | }) => {
26 | const intl = useIntl();
27 | const [disabledRole, setDisabledRole] = useState(false);
28 |
29 | // 当组件挂载或者record更新时,设置表单的初始值
30 | useEffect(() => {
31 | form.setFieldsValue(record);
32 |
33 | // 如果是管理员,则禁用角色选择
34 | if (record.id == 'admin') {
35 | setDisabledRole(true);
36 | } else {
37 | setDisabledRole(false);
38 | }
39 | console.log('record', record);
40 | });
41 |
42 | return (
43 |
209 | );
210 | };
211 |
212 | export default UpdateContent;
213 |
--------------------------------------------------------------------------------
/src/pages/Welcome.tsx:
--------------------------------------------------------------------------------
1 | import { getIndex } from '@/services/mj/api';
2 | import { PageContainer } from '@ant-design/pro-components';
3 | import { useIntl, useModel } from '@umijs/max';
4 | import { Alert, Card, Col, Divider, List, Row, Statistic, Tag, theme } from 'antd';
5 | import React, { useEffect, useState } from 'react';
6 |
7 | /**
8 | * 每个单独的卡片,为了复用样式抽成了组件
9 | * @param param0
10 | * @returns
11 | */
12 | const InfoCard: React.FC<{
13 | title: string;
14 | index: number;
15 | desc: string;
16 | href: string;
17 | }> = ({ title, href, index, desc }) => {
18 | const intl = useIntl();
19 | const { useToken } = theme;
20 |
21 | const { token } = useToken();
22 |
23 | return (
24 |
85 | );
86 | };
87 |
88 | const Welcome: React.FC = () => {
89 | const { token } = theme.useToken();
90 | const { initialState } = useModel('@@initialState');
91 | const intl = useIntl();
92 |
93 | // 是否显示注册
94 | const [data, setData] = useState();
95 | const [tops, setTops] = useState();
96 |
97 | useEffect(() => {
98 | getIndex().then((res) => {
99 | if (res.success) {
100 | if (res.data) {
101 | setData(res.data);
102 | const vs = Object.keys(res.data.tops).map((x) => {
103 | return {
104 | ip: x,
105 | count: res.data.tops[x],
106 | };
107 | });
108 | setTops(vs);
109 | }
110 | }
111 | });
112 | }, []);
113 |
114 | return (
115 |
116 | {data && data.notify && (
117 |
125 | )}
126 |
127 |
138 |
147 |
153 | {intl.formatMessage({ id: 'pages.welcome.link' })} Midjourney Proxy Admin
154 |
155 |
165 | {intl.formatMessage({ id: 'pages.welcome.description' })}
166 |
167 |
174 |
180 |
186 |
192 |
193 |
194 |
195 |
196 | {data && (
197 |
209 |
210 |
211 |
215 |
216 |
217 |
221 |
222 |
223 |
227 |
228 |
229 |
230 | {intl.formatMessage({ id: 'pages.welcome.top5' })}
231 | (
234 |
235 | {item.ip} {item.count} {intl.formatMessage({ id: 'pages.welcome.unit' })}
236 |
237 | )}
238 | />
239 |
240 | )}
241 |
242 | );
243 | };
244 |
245 | export default Welcome;
246 |
--------------------------------------------------------------------------------
/src/pages/components/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from 'antd';
2 |
3 | const MyModal = ({
4 | title,
5 | modalVisible,
6 | hideModal,
7 | modalContent,
8 | footer,
9 | modalWidth,
10 | }: {
11 | title: string;
12 | modalVisible: boolean;
13 | hideModal: () => void;
14 | modalContent: any;
15 | footer: any;
16 | modalWidth: number;
17 | }) => {
18 | return (
19 |
26 | {modalContent}
27 |
28 | );
29 | };
30 |
31 | export default MyModal;
32 |
--------------------------------------------------------------------------------
/src/requestErrorConfig.ts:
--------------------------------------------------------------------------------
1 | import type { RequestOptions } from '@@/plugin-request/request';
2 | import type { RequestConfig } from '@umijs/max';
3 | import { getLocale } from '@umijs/max';
4 | import { message, notification } from 'antd';
5 | import { history } from '@umijs/max';
6 |
7 | const loginPath = '/user/login';
8 |
9 |
10 | // 错误处理方案: 错误类型
11 | enum ErrorShowType {
12 | SILENT = 0,
13 | WARN_MESSAGE = 1,
14 | ERROR_MESSAGE = 2,
15 | NOTIFICATION = 3,
16 | REDIRECT = 9,
17 | }
18 | // 与后端约定的响应数据格式
19 | interface ResponseStructure {
20 | success: boolean;
21 | data: any;
22 | errorCode?: number;
23 | errorMessage?: string;
24 | showType?: ErrorShowType;
25 | }
26 |
27 | /**
28 | * @name 错误处理
29 | * pro 自带的错误处理, 可以在这里做自己的改动
30 | * @doc https://umijs.org/docs/max/request#配置
31 | */
32 | export const errorConfig: RequestConfig = {
33 | // 错误处理: umi@3 的错误处理方案。
34 | errorConfig: {
35 | // 错误抛出
36 | errorThrower: (res) => {
37 | const { success, data, errorCode, errorMessage, showType } =
38 | res as unknown as ResponseStructure;
39 | if (!success) {
40 | const error: any = new Error(errorMessage);
41 | error.name = 'BizError';
42 | error.info = { errorCode, errorMessage, showType, data };
43 | console.error('BizError', success, data, errorCode, errorMessage, showType, error);
44 | }
45 | },
46 | // 错误接收及处理
47 | errorHandler: (error: any, opts: any) => {
48 | if (opts?.skipErrorHandler) throw error;
49 | // 我们的 errorThrower 抛出的错误。
50 | if (error.name === 'BizError') {
51 | const errorInfo: ResponseStructure | undefined = error.info;
52 | if (errorInfo) {
53 | const { errorMessage, errorCode } = errorInfo;
54 | switch (errorInfo.showType) {
55 | case ErrorShowType.SILENT:
56 | // do nothing
57 | break;
58 | case ErrorShowType.WARN_MESSAGE:
59 | message.warning(errorMessage);
60 | break;
61 | case ErrorShowType.ERROR_MESSAGE:
62 | message.error(errorMessage);
63 | break;
64 | case ErrorShowType.NOTIFICATION:
65 | notification.open({
66 | description: errorMessage,
67 | message: errorCode,
68 | });
69 | break;
70 | case ErrorShowType.REDIRECT:
71 | // TODO: redirect
72 | break;
73 | default:
74 | if (errorMessage) {
75 | message.error(errorMessage);
76 | }
77 | }
78 | }
79 | } else if (error.response) {
80 | // Axios 的错误
81 | // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
82 | if (error.response.status === 403) {
83 | message.error('Forbidden, please login again.');
84 | }
85 | else if (error.response.status === 401) {
86 | message.error('Unauthorized, please login again.');
87 | // 去登录
88 | history.push(loginPath);
89 | }
90 | else if (error.response.status === 429) {
91 | message.error('Too many requests, please retry later.');
92 | } else {
93 | message.error(`Response status:${error.response.status}`);
94 | }
95 | } else if (error.request) {
96 | // 请求已经成功发起,但没有收到响应
97 | // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
98 | // 而在node.js中是 http.ClientRequest 的实例
99 | message.error('None response! Please retry.');
100 | } else {
101 | // 发送请求时出了点问题
102 | message.error('Request error, please retry.');
103 | }
104 | },
105 | },
106 |
107 | // 请求拦截器
108 | requestInterceptors: [
109 | (config: RequestOptions) => {
110 | const MJ_API_SECRET = sessionStorage.getItem('mj-api-secret') || ''; // 获取保存的密码
111 | const locale = getLocale();
112 | config.headers = {
113 | ...config.headers,
114 | 'mj-api-secret': MJ_API_SECRET, // 将密码作为自定义头部
115 | 'Accept-Language': locale === 'zh-CN' ? 'zh-CN' : 'en-US',
116 | };
117 | return { ...config };
118 | },
119 | ],
120 |
121 | timeout: 1000 * 60,
122 |
123 | responseInterceptors: [
124 | (response) => {
125 | // 你可以在这里添加响应拦截逻辑,移除默认的拦截逻辑
126 | const { data } = response;
127 | // 自定义拦截逻辑
128 | if (data && data?.success === false) {
129 | // 这里是你的自定义处理逻辑,可以根据需求进行调整
130 | message.error(data?.message || 'Request failed');
131 | }
132 | return response;
133 | },
134 | ],
135 | };
136 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 | /* eslint-disable no-underscore-dangle */
3 | /* globals workbox */
4 | workbox.core.setCacheNameDetails({
5 | prefix: 'antd-pro',
6 | suffix: 'v5',
7 | });
8 | // Control all opened tabs ASAP
9 | workbox.clientsClaim();
10 |
11 | /**
12 | * Use precaching list generated by workbox in build process.
13 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
14 | */
15 | workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
16 |
17 | /**
18 | * Register a navigation route.
19 | * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
20 | */
21 | workbox.routing.registerNavigationRoute('/index.html');
22 |
23 | /**
24 | * Use runtime cache:
25 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
26 | *
27 | * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
28 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
29 | */
30 |
31 | /** Handle API requests */
32 | workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
33 |
34 | /** Handle third party requests */
35 | workbox.routing.registerRoute(
36 | /^https:\/\/gw\.alipayobjects\.com\//,
37 | workbox.strategies.networkFirst(),
38 | );
39 | workbox.routing.registerRoute(
40 | /^https:\/\/cdnjs\.cloudflare\.com\//,
41 | workbox.strategies.networkFirst(),
42 | );
43 | workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
44 |
45 | /** Response to client after skipping waiting with MessageChannel */
46 | addEventListener('message', (event) => {
47 | const replyPort = event.ports[0];
48 | const message = event.data;
49 | if (replyPort && message && message.type === 'skip-waiting') {
50 | event.waitUntil(
51 | self.skipWaiting().then(
52 | () => {
53 | replyPort.postMessage({
54 | error: null,
55 | });
56 | },
57 | (error) => {
58 | replyPort.postMessage({
59 | error,
60 | });
61 | },
62 | ),
63 | );
64 | }
65 | });
66 |
--------------------------------------------------------------------------------
/src/services/mj/api.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | /* eslint-disable */
3 | import { request } from '@umijs/max';
4 |
5 | /** 获取当前的用户 GET /mj/admin/current */
6 | export async function currentUser(options?: { [key: string]: any }) {
7 | return request('/mj/admin/current', {
8 | method: 'GET',
9 | ...(options || {}),
10 | }).then((response) => {
11 | if (response && response.apiSecret) {
12 | sessionStorage.setItem('mj-api-secret', response.apiSecret);
13 | }
14 | if (response.imagePrefix) {
15 | sessionStorage.setItem('mj-image-prefix', response.imagePrefix);
16 | } else {
17 | sessionStorage.removeItem('mj-image-prefix');
18 | }
19 | return response;
20 | });
21 | }
22 |
23 | /** 退出登录接口 POST /mj/admin/logout */
24 | export async function outLogin(options?: { [key: string]: any }) {
25 | return request('/mj/admin/logout', {
26 | method: 'POST',
27 | ...(options || {}),
28 | }).then((response) => {
29 | sessionStorage.removeItem('mj-api-secret');
30 | sessionStorage.removeItem('mj-image-prefix');
31 | return response;
32 | });
33 | }
34 |
35 | /** 登录接口 POST /mj/admin/login */
36 | export async function login(body: any, options?: { [key: string]: any }) {
37 | return request('/mj/admin/login', {
38 | method: 'POST',
39 | headers: {
40 | 'Content-Type': 'application/json',
41 | },
42 | data: body,
43 | ...(options || {}),
44 | });
45 | }
46 |
47 | // 通过邮件注册接口
48 | export async function register(body: any, options?: { [key: string]: any }) {
49 | return request('/mj/admin/register', {
50 | method: 'POST',
51 | headers: {
52 | 'Content-Type': 'application/json',
53 | },
54 | data: body,
55 | ...(options || {}),
56 | });
57 | }
58 |
59 | // 首页信息
60 | export async function getIndex(options?: { [key: string]: any }) {
61 | return request('/mj/home', {
62 | method: 'GET',
63 | ...(options || {}),
64 | });
65 | }
66 |
67 | /** MJ 接口 */
68 |
69 | /** POST /mj/account/create */
70 | export async function createAccount(data: object, options?: { [key: string]: any }) {
71 | return request('/mj/admin/account', {
72 | method: 'POST',
73 | data: data,
74 | ...(options || {}),
75 | });
76 | }
77 |
78 | /** POST /mj/account/query */
79 | export async function queryAccount(options?: { [key: string]: any }) {
80 | return request>('/mj/admin/accounts', {
81 | method: 'GET',
82 | ...(options || {}),
83 | });
84 | }
85 |
86 | export async function queryAccounts(
87 | data: any,
88 | predicate?: string,
89 | descend?: boolean,
90 | options?: { [key: string]: any },
91 | ) {
92 | return request>('/mj/admin/accounts', {
93 | method: 'POST',
94 | data: {
95 | pagination: {
96 | current: data?.current || 1,
97 | pageSize: data?.pageSize || 10,
98 | },
99 | sort: {
100 | predicate: predicate || '',
101 | reverse: descend,
102 | },
103 | search: {
104 | ...data,
105 | },
106 | },
107 | ...(options || {}),
108 | });
109 | }
110 |
111 | /** POST /mj/account/{id}/sync-info */
112 | export async function refreshAccount(id: string, options?: { [key: string]: any }) {
113 | return request(`/mj/admin/account-sync/${id}`, {
114 | method: 'POST',
115 | ...(options || {}),
116 | });
117 | }
118 |
119 | // loginAccountGetToken
120 | export async function loginAccountGetToken(id: string, options?: { [key: string]: any }) {
121 | return request(`/mj/admin/account-login/${id}`, {
122 | method: 'POST',
123 | ...(options || {}),
124 | });
125 | }
126 |
127 | /** PUT */
128 | export async function updateAndReconnect(
129 | id: string,
130 | data: object,
131 | options?: { [key: string]: any },
132 | ) {
133 | return request(`/mj/admin/account-reconnect/${id}`, {
134 | method: 'PUT',
135 | data: data,
136 | ...(options || {}),
137 | });
138 | }
139 |
140 | export async function update(id: string, data: object, options?: { [key: string]: any }) {
141 | return request(`/mj/admin/account/${id}`, {
142 | method: 'PUT',
143 | data: data,
144 | ...(options || {}),
145 | });
146 | }
147 |
148 | // CF 标记验证通过
149 | export async function accountCfOk(id: string, data?: object, options?: { [key: string]: any }) {
150 | return request(`/mj/admin/account-cf/${id}`, {
151 | method: 'POST',
152 | data: data,
153 | ...(options || {}),
154 | });
155 | }
156 |
157 | // CF 刷新链接
158 | export async function accountCfUrl(id: string, options?: { [key: string]: any }) {
159 | return request(`/mj/admin/account-cf/${id}?refresh=true`, {
160 | method: 'GET',
161 | ...(options || {}),
162 | });
163 | }
164 |
165 | /** DELETE */
166 | export async function deleteAccount(id: string, options?: { [key: string]: any }) {
167 | return request(`/mj/admin/account/${id}`, {
168 | method: 'DELETE',
169 | ...(options || {}),
170 | });
171 | }
172 |
173 | /** DELETE */
174 | export async function deleteTask(id: string, options?: { [key: string]: any }) {
175 | return request(`/mj/admin/task/${id}`, {
176 | method: 'DELETE',
177 | ...(options || {}),
178 | });
179 | }
180 |
181 | export async function accountChangeVersion(
182 | id: string,
183 | version: string,
184 | options?: { [key: string]: any },
185 | ) {
186 | return request(`/mj/admin/account-change-version/${id}?version=${version}`, {
187 | method: 'POST',
188 | ...(options || {}),
189 | });
190 | }
191 |
192 | export async function accountAction(
193 | id: string,
194 | botType: string,
195 | customId: string,
196 | options?: { [key: string]: any },
197 | ) {
198 | return request(
199 | `/mj/admin/account-action/${id}?customId=${customId}&botType=${botType}`,
200 | {
201 | method: 'POST',
202 | ...(options || {}),
203 | },
204 | );
205 | }
206 |
207 | export async function queryTask(data: any, options?: { [key: string]: any }) {
208 | return request>('/mj/admin/tasks', {
209 | method: 'POST',
210 | data: {
211 | pagination: {
212 | current: data?.current || 1,
213 | pageSize: data?.pageSize || 10,
214 | },
215 | sort: {
216 | predicate: '',
217 | reverse: true,
218 | },
219 | search: {
220 | ...data,
221 | },
222 | },
223 | ...(options || {}),
224 | });
225 | }
226 |
227 | /** 用户列表 */
228 | export async function queryUser(data: any, options?: { [key: string]: any }) {
229 | return request>('/mj/admin/users', {
230 | method: 'POST',
231 | data: {
232 | pagination: {
233 | current: data?.current || 1,
234 | pageSize: data?.pageSize || 10,
235 | },
236 | sort: {
237 | predicate: '',
238 | reverse: true,
239 | },
240 | search: {
241 | ...data,
242 | },
243 | },
244 | ...(options || {}),
245 | });
246 | }
247 |
248 | /** 删除用户 */
249 | export async function deleteUser(id: string, options?: { [key: string]: any }) {
250 | return request(`/mj/admin/user/${id}`, {
251 | method: 'DELETE',
252 | ...(options || {}),
253 | });
254 | }
255 |
256 | /** 创建/编辑用户 */
257 | export async function createUser(data: object, options?: { [key: string]: any }) {
258 | return request('/mj/admin/user', {
259 | method: 'POST',
260 | data: data,
261 | ...(options || {}),
262 | });
263 | }
264 |
265 | /** 领域列表 */
266 | export async function queryDomain(data: any, options?: { [key: string]: any }) {
267 | return request>('/mj/admin/domain-tags', {
268 | method: 'POST',
269 | data: {
270 | pagination: {
271 | current: data?.current || 1,
272 | pageSize: data?.pageSize || 10,
273 | },
274 | sort: {
275 | predicate: '',
276 | reverse: true,
277 | },
278 | search: {
279 | ...data,
280 | },
281 | },
282 | ...(options || {}),
283 | });
284 | }
285 |
286 | /** 删除领域 */
287 | export async function deleteDomain(id: string, options?: { [key: string]: any }) {
288 | return request(`/mj/admin/domain-tag/${id}`, {
289 | method: 'DELETE',
290 | ...(options || {}),
291 | });
292 | }
293 | /** 所有领域 */
294 | export async function allDomain(options?: { [key: string]: any }) {
295 | return request('/mj/admin/domain-tags', {
296 | method: 'GET',
297 | ...(options || {}),
298 | });
299 | }
300 |
301 | /** 创建/编辑领域 */
302 | export async function createDomain(data: object, options?: { [key: string]: any }) {
303 | return request('/mj/admin/domain-tag', {
304 | method: 'POST',
305 | data: data,
306 | ...(options || {}),
307 | });
308 | }
309 |
310 | /**
311 | * 违规词
312 | * @param data
313 | * @param options
314 | * @returns
315 | */
316 | export async function queryIllegalWord(data: any, options?: { [key: string]: any }) {
317 | return request>('/mj/admin/banned-words', {
318 | method: 'POST',
319 | data: {
320 | pagination: {
321 | current: data?.current || 1,
322 | pageSize: data?.pageSize || 10,
323 | },
324 | sort: {
325 | predicate: '',
326 | reverse: true,
327 | },
328 | search: {
329 | ...data,
330 | },
331 | },
332 | ...(options || {}),
333 | });
334 | }
335 |
336 | /**
337 | * 删除违规词
338 | * @param id
339 | * @param options
340 | * @returns
341 | */
342 | export async function deleteIllegalWord(id: string, options?: { [key: string]: any }) {
343 | return request(`/mj/admin/banned-word/${id}`, {
344 | method: 'DELETE',
345 | ...(options || {}),
346 | });
347 | }
348 |
349 | /**
350 | * 创建违规词
351 | * @param data
352 | * @param options
353 | * @returns
354 | */
355 | export async function createIllegalWord(data: object, options?: { [key: string]: any }) {
356 | return request('/mj/admin/banned-word', {
357 | method: 'POST',
358 | data: data,
359 | ...(options || {}),
360 | });
361 | }
362 |
363 | export async function queryTaskByIds(ids: string[], options?: { [key: string]: any }) {
364 | return request('/mj/task/list-by-ids', {
365 | method: 'POST',
366 | data: { ids: ids },
367 | ...(options || {}),
368 | });
369 | }
370 |
371 | export async function getTask(id: string, options?: { [key: string]: any }) {
372 | return request(`/mj/task-admin/${id}/fetch`, {
373 | method: 'GET',
374 | ...(options || {}),
375 | });
376 | }
377 |
378 | export async function submitTask(action: string, data: object, options?: { [key: string]: any }) {
379 | return request(`/mj/submit/${action}`, {
380 | method: 'POST',
381 | data: data,
382 | ...(options || {}),
383 | });
384 | }
385 |
386 | export async function submitShow(action: string, data: object, options?: { [key: string]: any }) {
387 | return request(`/mj/submit/${action}`, {
388 | method: 'POST',
389 | data: data,
390 | ...(options || {}),
391 | });
392 | }
393 |
394 | export async function cancelTask(id: string, options?: { [key: string]: any }) {
395 | return request(`/mj/task/${id}/cancel`, {
396 | method: 'POST',
397 | ...(options || {}),
398 | });
399 | }
400 |
401 | export async function swapFace(data: object, options?: { [key: string]: any }) {
402 | return request('/mj/insight-face/swap', {
403 | method: 'POST',
404 | data: data,
405 | ...(options || {}),
406 | });
407 | }
408 |
409 | export async function swapVideoFace(data: object, options?: { [key: string]: any }) {
410 | return request('/mj/insight-face/video-swap', {
411 | method: 'POST',
412 | data: data,
413 | ...(options || {}),
414 | });
415 | }
416 |
417 | export async function probe(tail: number, level = 0, options?: { [key: string]: any }) {
418 | return request('/mj/admin/probe?tail=' + tail + '&level=' + level, {
419 | method: 'GET',
420 | ...(options || {}),
421 | });
422 | }
423 |
424 | // 获取配置
425 | export async function getConfig(options?: { [key: string]: any }) {
426 | return request('/mj/admin/setting', {
427 | method: 'GET',
428 | ...(options || {}),
429 | });
430 | }
431 |
432 | // 修改配置
433 | export async function updateConfig(data: object, options?: { [key: string]: any }) {
434 | return request('/mj/admin/setting', {
435 | method: 'POST',
436 | data: data,
437 | ...(options || {}),
438 | });
439 | }
440 |
441 | // mongo db 连接
442 | export async function mongoConnect() {
443 | return request('/mj/admin/verify-mongo', {
444 | method: 'POST',
445 | });
446 | }
447 |
448 | // 一键迁移
449 | export async function migrateAccountAndTasks(data: object, options?: { [key: string]: any }) {
450 | return request('/mj/admin/mjplus-migration', {
451 | method: 'POST',
452 | data: data,
453 | ...(options || {}),
454 | });
455 | }
456 |
--------------------------------------------------------------------------------
/src/services/mj/index.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | /* eslint-disable */
3 | // API 更新时间:
4 | // API 唯一标识:
5 | import * as api from './api';
6 | export default {
7 | api,
8 | };
9 |
--------------------------------------------------------------------------------
/src/services/mj/typings.d.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | /* eslint-disable */
3 |
4 | declare namespace API {
5 | type CurrentUser = {
6 | name?: string;
7 | apiSecret?: string;
8 | imagePrefix?: string;
9 | active?: boolean = false;
10 | version?: string;
11 | avatar?: string;
12 | userid?: string;
13 | email?: string;
14 | signature?: string;
15 | title?: string;
16 | group?: string;
17 | tags?: { key?: string; label?: string }[];
18 | notifyCount?: number;
19 | unreadCount?: number;
20 | country?: string;
21 | access?: string;
22 | geographic?: {
23 | province?: { label?: string; key?: string };
24 | city?: { label?: string; key?: string };
25 | };
26 | address?: string;
27 | phone?: string;
28 | };
29 |
30 | type LoginResult = {
31 | code?: number;
32 | apiSecret?: string;
33 | description?: string;
34 | };
35 |
36 | type ReturnMessage = {
37 | code?: number;
38 | description?: string;
39 | result?: any;
40 | };
41 |
42 | type Result = {
43 | code: number;
44 | message?: string;
45 | data?: T;
46 | success?: boolean;
47 | timestamp?: string;
48 | }
49 |
50 | type PageParams = {
51 | current?: number;
52 | pageSize?: number;
53 | };
54 |
55 | type RuleListItem = {
56 | key?: number;
57 | disabled?: boolean;
58 | href?: string;
59 | avatar?: string;
60 | name?: string;
61 | owner?: string;
62 | desc?: string;
63 | callNo?: number;
64 | status?: number;
65 | updatedAt?: string;
66 | createdAt?: string;
67 | progress?: number;
68 | };
69 |
70 | type RuleList = {
71 | data?: RuleListItem[];
72 | /** 列表的内容总数 */
73 | total?: number;
74 | success?: boolean;
75 | };
76 |
77 | type FakeCaptcha = {
78 | code?: number;
79 | status?: string;
80 | };
81 |
82 | type LoginParams = {
83 | username?: string;
84 | password?: string;
85 | };
86 |
87 | type ErrorResponse = {
88 | /** 业务约定的错误码 */
89 | errorCode: string;
90 | /** 业务上的错误信息 */
91 | errorMessage?: string;
92 | /** 业务上的请求是否成功 */
93 | success?: boolean;
94 | };
95 |
96 | type NoticeIconList = {
97 | data?: NoticeIconItem[];
98 | /** 列表的内容总数 */
99 | total?: number;
100 | success?: boolean;
101 | };
102 |
103 | type NoticeIconItemType = 'notification' | 'message' | 'event';
104 |
105 | type NoticeIconItem = {
106 | id?: string;
107 | extra?: string;
108 | key?: string;
109 | read?: boolean;
110 | avatar?: string;
111 | title?: string;
112 | status?: string;
113 | datetime?: string;
114 | description?: string;
115 | type?: NoticeIconItemType;
116 | };
117 | }
118 |
--------------------------------------------------------------------------------
/src/services/swagger/index.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | /* eslint-disable */
3 | // API 更新时间:
4 | // API 唯一标识:
5 | import * as pet from './pet';
6 | import * as store from './store';
7 | import * as user from './user';
8 | export default {
9 | pet,
10 | store,
11 | user,
12 | };
13 |
--------------------------------------------------------------------------------
/src/services/swagger/pet.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | /* eslint-disable */
3 | import { request } from '@umijs/max';
4 |
5 | /** Update an existing pet PUT /pet */
6 | export async function updatePet(body: API.Pet, options?: { [key: string]: any }) {
7 | return request('/pet', {
8 | method: 'PUT',
9 | headers: {
10 | 'Content-Type': 'application/json',
11 | },
12 | data: body,
13 | ...(options || {}),
14 | });
15 | }
16 |
17 | /** Add a new pet to the store POST /pet */
18 | export async function addPet(body: API.Pet, options?: { [key: string]: any }) {
19 | return request('/pet', {
20 | method: 'POST',
21 | headers: {
22 | 'Content-Type': 'application/json',
23 | },
24 | data: body,
25 | ...(options || {}),
26 | });
27 | }
28 |
29 | /** Find pet by ID Returns a single pet GET /pet/${param0} */
30 | export async function getPetById(
31 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
32 | params: API.getPetByIdParams,
33 | options?: { [key: string]: any },
34 | ) {
35 | const { petId: param0, ...queryParams } = params;
36 | return request(`/pet/${param0}`, {
37 | method: 'GET',
38 | params: { ...queryParams },
39 | ...(options || {}),
40 | });
41 | }
42 |
43 | /** Updates a pet in the store with form data POST /pet/${param0} */
44 | export async function updatePetWithForm(
45 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
46 | params: API.updatePetWithFormParams,
47 | body: { name?: string; status?: string },
48 | options?: { [key: string]: any },
49 | ) {
50 | const { petId: param0, ...queryParams } = params;
51 | const formData = new FormData();
52 |
53 | Object.keys(body).forEach((ele) => {
54 | const item = (body as any)[ele];
55 |
56 | if (item !== undefined && item !== null) {
57 | formData.append(
58 | ele,
59 | typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
60 | );
61 | }
62 | });
63 |
64 | return request(`/pet/${param0}`, {
65 | method: 'POST',
66 | params: { ...queryParams },
67 | data: formData,
68 | ...(options || {}),
69 | });
70 | }
71 |
72 | /** Deletes a pet DELETE /pet/${param0} */
73 | export async function deletePet(
74 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
75 | params: API.deletePetParams & {
76 | // header
77 | api_key?: string;
78 | },
79 | options?: { [key: string]: any },
80 | ) {
81 | const { petId: param0, ...queryParams } = params;
82 | return request(`/pet/${param0}`, {
83 | method: 'DELETE',
84 | headers: {},
85 | params: { ...queryParams },
86 | ...(options || {}),
87 | });
88 | }
89 |
90 | /** uploads an image POST /pet/${param0}/uploadImage */
91 | export async function uploadFile(
92 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
93 | params: API.uploadFileParams,
94 | body: { additionalMetadata?: string; file?: string },
95 | file?: File,
96 | options?: { [key: string]: any },
97 | ) {
98 | const { petId: param0, ...queryParams } = params;
99 | const formData = new FormData();
100 |
101 | if (file) {
102 | formData.append('file', file);
103 | }
104 |
105 | Object.keys(body).forEach((ele) => {
106 | const item = (body as any)[ele];
107 |
108 | if (item !== undefined && item !== null) {
109 | formData.append(
110 | ele,
111 | typeof item === 'object' && !(item instanceof File) ? JSON.stringify(item) : item,
112 | );
113 | }
114 | });
115 |
116 | return request(`/pet/${param0}/uploadImage`, {
117 | method: 'POST',
118 | params: { ...queryParams },
119 | data: formData,
120 | requestType: 'form',
121 | ...(options || {}),
122 | });
123 | }
124 |
125 | /** Finds Pets by status Multiple status values can be provided with comma separated strings GET /pet/findByStatus */
126 | export async function findPetsByStatus(
127 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
128 | params: API.findPetsByStatusParams,
129 | options?: { [key: string]: any },
130 | ) {
131 | return request('/pet/findByStatus', {
132 | method: 'GET',
133 | params: {
134 | ...params,
135 | },
136 | ...(options || {}),
137 | });
138 | }
139 |
140 | /** Finds Pets by tags Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. GET /pet/findByTags */
141 | export async function findPetsByTags(
142 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
143 | params: API.findPetsByTagsParams,
144 | options?: { [key: string]: any },
145 | ) {
146 | return request('/pet/findByTags', {
147 | method: 'GET',
148 | params: {
149 | ...params,
150 | },
151 | ...(options || {}),
152 | });
153 | }
154 |
--------------------------------------------------------------------------------
/src/services/swagger/store.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | /* eslint-disable */
3 | import { request } from '@umijs/max';
4 |
5 | /** Returns pet inventories by status Returns a map of status codes to quantities GET /store/inventory */
6 | export async function getInventory(options?: { [key: string]: any }) {
7 | return request>('/store/inventory', {
8 | method: 'GET',
9 | ...(options || {}),
10 | });
11 | }
12 |
13 | /** Place an order for a pet POST /store/order */
14 | export async function placeOrder(body: API.Order, options?: { [key: string]: any }) {
15 | return request('/store/order', {
16 | method: 'POST',
17 | data: body,
18 | ...(options || {}),
19 | });
20 | }
21 |
22 | /** Find purchase order by ID For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions GET /store/order/${param0} */
23 | export async function getOrderById(
24 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
25 | params: API.getOrderByIdParams,
26 | options?: { [key: string]: any },
27 | ) {
28 | const { orderId: param0, ...queryParams } = params;
29 | return request(`/store/order/${param0}`, {
30 | method: 'GET',
31 | params: { ...queryParams },
32 | ...(options || {}),
33 | });
34 | }
35 |
36 | /** Delete purchase order by ID For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors DELETE /store/order/${param0} */
37 | export async function deleteOrder(
38 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
39 | params: API.deleteOrderParams,
40 | options?: { [key: string]: any },
41 | ) {
42 | const { orderId: param0, ...queryParams } = params;
43 | return request(`/store/order/${param0}`, {
44 | method: 'DELETE',
45 | params: { ...queryParams },
46 | ...(options || {}),
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/src/services/swagger/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace API {
2 | type ApiResponse = {
3 | code?: number;
4 | type?: string;
5 | message?: string;
6 | };
7 |
8 | type Category = {
9 | id?: number;
10 | name?: string;
11 | };
12 |
13 | type deleteOrderParams = {
14 | /** ID of the order that needs to be deleted */
15 | orderId: number;
16 | };
17 |
18 | type deletePetParams = {
19 | api_key?: string;
20 | /** Pet id to delete */
21 | petId: number;
22 | };
23 |
24 | type deleteUserParams = {
25 | /** The name that needs to be deleted */
26 | username: string;
27 | };
28 |
29 | type findPetsByStatusParams = {
30 | /** Status values that need to be considered for filter */
31 | status: ('available' | 'pending' | 'sold')[];
32 | };
33 |
34 | type findPetsByTagsParams = {
35 | /** Tags to filter by */
36 | tags: string[];
37 | };
38 |
39 | type getOrderByIdParams = {
40 | /** ID of pet that needs to be fetched */
41 | orderId: number;
42 | };
43 |
44 | type getPetByIdParams = {
45 | /** ID of pet to return */
46 | petId: number;
47 | };
48 |
49 | type getUserByNameParams = {
50 | /** The name that needs to be fetched. Use user1 for testing. */
51 | username: string;
52 | };
53 |
54 | type loginUserParams = {
55 | /** The user name for login */
56 | username: string;
57 | /** The password for login in clear text */
58 | password: string;
59 | };
60 |
61 | type Order = {
62 | id?: number;
63 | petId?: number;
64 | quantity?: number;
65 | shipDate?: string;
66 | /** Order Status */
67 | status?: 'placed' | 'approved' | 'delivered';
68 | complete?: boolean;
69 | };
70 |
71 | type Pet = {
72 | id?: number;
73 | category?: Category;
74 | name: string;
75 | photoUrls: string[];
76 | tags?: Tag[];
77 | /** pet status in the store */
78 | status?: 'available' | 'pending' | 'sold';
79 | };
80 |
81 | type Tag = {
82 | id?: number;
83 | name?: string;
84 | };
85 |
86 | type updatePetWithFormParams = {
87 | /** ID of pet that needs to be updated */
88 | petId: number;
89 | };
90 |
91 | type updateUserParams = {
92 | /** name that need to be updated */
93 | username: string;
94 | };
95 |
96 | type uploadFileParams = {
97 | /** ID of pet to update */
98 | petId: number;
99 | };
100 |
101 | type User = {
102 | id?: number;
103 | username?: string;
104 | firstName?: string;
105 | lastName?: string;
106 | email?: string;
107 | password?: string;
108 | phone?: string;
109 | /** User Status */
110 | userStatus?: number;
111 | };
112 | }
113 |
--------------------------------------------------------------------------------
/src/services/swagger/user.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | /* eslint-disable */
3 | import { request } from '@umijs/max';
4 |
5 | /** Create user This can only be done by the logged in user. POST /user */
6 | export async function createUser(body: API.User, options?: { [key: string]: any }) {
7 | return request('/user', {
8 | method: 'POST',
9 | data: body,
10 | ...(options || {}),
11 | });
12 | }
13 |
14 | /** Get user by user name GET /user/${param0} */
15 | export async function getUserByName(
16 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
17 | params: API.getUserByNameParams,
18 | options?: { [key: string]: any },
19 | ) {
20 | const { username: param0, ...queryParams } = params;
21 | return request(`/user/${param0}`, {
22 | method: 'GET',
23 | params: { ...queryParams },
24 | ...(options || {}),
25 | });
26 | }
27 |
28 | /** Updated user This can only be done by the logged in user. PUT /user/${param0} */
29 | export async function updateUser(
30 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
31 | params: API.updateUserParams,
32 | body: API.User,
33 | options?: { [key: string]: any },
34 | ) {
35 | const { username: param0, ...queryParams } = params;
36 | return request(`/user/${param0}`, {
37 | method: 'PUT',
38 | params: { ...queryParams },
39 | data: body,
40 | ...(options || {}),
41 | });
42 | }
43 |
44 | /** Delete user This can only be done by the logged in user. DELETE /user/${param0} */
45 | export async function deleteUser(
46 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
47 | params: API.deleteUserParams,
48 | options?: { [key: string]: any },
49 | ) {
50 | const { username: param0, ...queryParams } = params;
51 | return request(`/user/${param0}`, {
52 | method: 'DELETE',
53 | params: { ...queryParams },
54 | ...(options || {}),
55 | });
56 | }
57 |
58 | /** Creates list of users with given input array POST /user/createWithArray */
59 | export async function createUsersWithArrayInput(
60 | body: API.User[],
61 | options?: { [key: string]: any },
62 | ) {
63 | return request('/user/createWithArray', {
64 | method: 'POST',
65 | data: body,
66 | ...(options || {}),
67 | });
68 | }
69 |
70 | /** Creates list of users with given input array POST /user/createWithList */
71 | export async function createUsersWithListInput(body: API.User[], options?: { [key: string]: any }) {
72 | return request('/user/createWithList', {
73 | method: 'POST',
74 | data: body,
75 | ...(options || {}),
76 | });
77 | }
78 |
79 | /** Logs user into the system GET /user/login */
80 | export async function loginUser(
81 | // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
82 | params: API.loginUserParams,
83 | options?: { [key: string]: any },
84 | ) {
85 | return request('/user/login', {
86 | method: 'GET',
87 | params: {
88 | ...params,
89 | },
90 | ...(options || {}),
91 | });
92 | }
93 |
94 | /** Logs out current logged in user session GET /user/logout */
95 | export async function logoutUser(options?: { [key: string]: any }) {
96 | return request('/user/logout', {
97 | method: 'GET',
98 | ...(options || {}),
99 | });
100 | }
101 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'slash2';
2 | declare module '*.css';
3 | declare module '*.less';
4 | declare module '*.scss';
5 | declare module '*.sass';
6 | declare module '*.svg';
7 | declare module '*.png';
8 | declare module '*.jpg';
9 | declare module '*.jpeg';
10 | declare module '*.gif';
11 | declare module '*.bmp';
12 | declare module '*.tiff';
13 | declare module 'omit.js';
14 | declare module 'numeral';
15 | declare module '@antv/data-set';
16 | declare module 'mockjs';
17 | declare module 'react-fittext';
18 | declare module 'bizcharts-plugin-slider';
19 |
20 | declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
21 |
--------------------------------------------------------------------------------
/tests/setupTests.jsx:
--------------------------------------------------------------------------------
1 | const localStorageMock = {
2 | getItem: jest.fn(),
3 | setItem: jest.fn(),
4 | removeItem: jest.fn(),
5 | clear: jest.fn(),
6 | };
7 |
8 | global.localStorage = localStorageMock;
9 |
10 | Object.defineProperty(URL, 'createObjectURL', {
11 | writable: true,
12 | value: jest.fn(),
13 | });
14 |
15 | class Worker {
16 | constructor(stringUrl) {
17 | this.url = stringUrl;
18 | this.onmessage = () => {};
19 | }
20 |
21 | postMessage(msg) {
22 | this.onmessage(msg);
23 | }
24 | }
25 | window.Worker = Worker;
26 |
27 | /* eslint-disable global-require */
28 | if (typeof window !== 'undefined') {
29 | // ref: https://github.com/ant-design/ant-design/issues/18774
30 | if (!window.matchMedia) {
31 | Object.defineProperty(global.window, 'matchMedia', {
32 | writable: true,
33 | configurable: true,
34 | value: jest.fn(() => ({
35 | matches: false,
36 | addListener: jest.fn(),
37 | removeListener: jest.fn(),
38 | })),
39 | });
40 | }
41 | if (!window.matchMedia) {
42 | Object.defineProperty(global.window, 'matchMedia', {
43 | writable: true,
44 | configurable: true,
45 | value: jest.fn((query) => ({
46 | matches: query.includes('max-width'),
47 | addListener: jest.fn(),
48 | removeListener: jest.fn(),
49 | })),
50 | });
51 | }
52 | }
53 | const errorLog = console.error;
54 | Object.defineProperty(global.window.console, 'error', {
55 | writable: true,
56 | configurable: true,
57 | value: (...rest) => {
58 | const logStr = rest.join('');
59 | if (logStr.includes('Warning: An update to %s inside a test was not wrapped in act(...)')) {
60 | return;
61 | }
62 | errorLog(...rest);
63 | },
64 | });
65 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "preserve",
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "baseUrl": "./",
11 | "skipLibCheck": true,
12 | "experimentalDecorators": true,
13 | "strict": true,
14 | "resolveJsonModule": true,
15 | "allowSyntheticDefaultImports": true,
16 | "paths": {
17 | "@/*": ["./src/*"],
18 | "@@/*": ["./src/.umi/*"],
19 | "@@test/*": ["./src/.umi-test/*"]
20 | }
21 | },
22 | "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"]
23 | }
24 |
--------------------------------------------------------------------------------
/types/cache/cache.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/types/cache/mock/mock.cache.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trueai-org/midjourney-proxy-webui/1ceaa67750eb8e4db1e4cb38a3b8f0aca6e53cad/types/cache/mock/mock.cache.js
--------------------------------------------------------------------------------