create-pardx-scaffold 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/apps/api/config.local.yaml +1 -89
- package/template/apps/api/libs/infra/common/config/dto/config.dto.ts +0 -5
- package/template/apps/api/libs/infra/common/config/validation/index.ts +0 -2
- package/template/apps/api/libs/infra/common/config/validation/yaml.validation.ts +0 -33
- package/template/apps/api/prisma.config.ts +29 -0
- package/template/apps/web/.env.example +1 -1
- package/template/apps/web/config.ts +8 -7
- package/template/apps/web/hooks/useNotificationSSE.ts +2 -1
- package/template/apps/web/hooks/useVersionCheck.ts +2 -1
- package/template/apps/web/i18n/config.ts +0 -1
- package/template/apps/web/i18n/types.ts +0 -3
- package/template/apps/web/lib/analytics/components/PageTracker.tsx +13 -11
- package/template/apps/web/lib/api/hooks/use-python-task.ts +2 -1
- package/template/apps/web/proxy.ts +1 -41
- package/template/packages/utils/string.util.ts +0 -8
- package/template/scripts/init-project.js +54 -30
- package/template/apps/web/lib/config.ts +0 -163
package/package.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
app:
|
|
2
2
|
name: 'pardx'
|
|
3
|
-
ChineseName: 'Pardx'
|
|
4
3
|
version: '2.1.0'
|
|
5
4
|
apiVersion: '1' # API 版本号
|
|
6
5
|
minClientVersion: '0.0.1' # 最低兼容前端版本
|
|
@@ -11,17 +10,9 @@ app:
|
|
|
11
10
|
MaxPageSize: 500
|
|
12
11
|
defaultPageSize: 100
|
|
13
12
|
defaultMiniPageSize: 30
|
|
14
|
-
canCreateTrail: true
|
|
15
13
|
defaultVendor: 'tos'
|
|
16
14
|
defaultBucketPublic: false
|
|
17
|
-
enableRetryMechanism: true
|
|
18
|
-
enableEnhancedLogging: true
|
|
19
|
-
maxRetries: 3
|
|
20
|
-
baseRetryDelay: 1000
|
|
21
15
|
nestLogOutput: 'both'
|
|
22
|
-
audioTranscribe:
|
|
23
|
-
statusUpdateMode: 'polling'
|
|
24
|
-
pollingInterval: 30
|
|
25
16
|
zones:
|
|
26
17
|
- zone: 'cn'
|
|
27
18
|
locale: 'zh-CN'
|
|
@@ -29,9 +20,6 @@ app:
|
|
|
29
20
|
defaultPublicBucket: 'tos-pardx-files'
|
|
30
21
|
transcodeBucket: 'tos-pardx-files'
|
|
31
22
|
|
|
32
|
-
uploadConfig:
|
|
33
|
-
chrunkSize: 8388608 #1024 * 1024 * 8
|
|
34
|
-
|
|
35
23
|
ipinfo:
|
|
36
24
|
url: 'https://ipinfo.io'
|
|
37
25
|
token: 'f23c2d43721815'
|
|
@@ -107,9 +95,6 @@ rateLimit:
|
|
|
107
95
|
userIds: [] # 管理员用户 ID
|
|
108
96
|
apiKeys: [] # 内部服务 API Key
|
|
109
97
|
|
|
110
|
-
pinecone:
|
|
111
|
-
apiKey: 'cd14c373-cf91-4857-bdba-8384386193d5'
|
|
112
|
-
|
|
113
98
|
# jwt 配置
|
|
114
99
|
jwt:
|
|
115
100
|
secret: '0EHAmnd9i82n'
|
|
@@ -150,18 +135,9 @@ redis:
|
|
|
150
135
|
- name: 'shortCode'
|
|
151
136
|
key: 'pardx:shortCode:'
|
|
152
137
|
expireIn: -1
|
|
153
|
-
- name: 'dailyQuestion'
|
|
154
|
-
key: 'pardx:daily-question:'
|
|
155
|
-
expireIn: 86400 # 24小时 (每天过期)
|
|
156
|
-
- name: 'checkInStatus'
|
|
157
|
-
key: 'pardx:check-in-status:'
|
|
158
|
-
expireIn: 86400 # 24小时 (每天过期)
|
|
159
138
|
- name: 'emailCodeDevice'
|
|
160
139
|
key: 'pardx:emailCode:device:'
|
|
161
140
|
expireIn: 180 # 3分钟
|
|
162
|
-
- name: 'emailCodeEmail'
|
|
163
|
-
key: 'pardx:emailCode:Email:'
|
|
164
|
-
expireIn: 180 # 3分钟
|
|
165
141
|
- name: 'emailCodePerDay'
|
|
166
142
|
key: 'pardx:emailCode:EmailNumPerDay:'
|
|
167
143
|
expireIn: 86400
|
|
@@ -179,93 +155,29 @@ redis:
|
|
|
179
155
|
- name: 'subscription'
|
|
180
156
|
key: 'pardx:subscription:'
|
|
181
157
|
expireIn: 600 # 10分钟
|
|
182
|
-
- name: 'subscriptionList'
|
|
183
|
-
key: 'pardx:subscription:list:'
|
|
184
|
-
expireIn: 300 # 5分钟(列表缓存时间较短,保证数据新鲜度)
|
|
185
|
-
- name: 'notificationList'
|
|
186
|
-
key: 'pardx:notification:list:'
|
|
187
|
-
expireIn: 300 # 5分钟(通知列表缓存时间较短,保证数据新鲜度)
|
|
188
|
-
- name: 'notificationStats'
|
|
189
|
-
key: 'pardx:notification:stats:'
|
|
190
|
-
expireIn: 60 # 1分钟(统计数据更新频繁,缓存时间更短)
|
|
191
158
|
- name: 'privateDownloadUrl'
|
|
192
159
|
key: 'pardx:file:fileurl:'
|
|
193
160
|
expireIn: 30 #1小时
|
|
194
161
|
- name: 'ipinfo'
|
|
195
162
|
key: 'pardx:ipinfo:ip:'
|
|
196
163
|
expireIn: 2592000 #30天
|
|
197
|
-
- name: 'transferSaveForlder'
|
|
198
|
-
key: 'pardx:transferSave:folderIds:'
|
|
199
|
-
expireIn: 86400
|
|
200
|
-
- name: 'recycleBinTask'
|
|
201
|
-
key: 'pardx:recycleTask:fileSystemIds:'
|
|
202
|
-
expireIn: 86400
|
|
203
|
-
- name: 'moveFolder'
|
|
204
|
-
key: 'pardx:moveFolder:folderIds:'
|
|
205
|
-
expireIn: 86400
|
|
206
|
-
# Long-term Memory System Cache (长期记忆系统缓存)
|
|
207
|
-
- name: 'userPreferences'
|
|
208
|
-
key: 'pardx:memory:preferences:'
|
|
209
|
-
expireIn: 300 # 5分钟 (参考 Python 最佳实践)
|
|
210
|
-
- name: 'memoryStats'
|
|
211
|
-
key: 'pardx:memory:stats:'
|
|
212
|
-
expireIn: 300 # 5分钟
|
|
213
|
-
- name: 'preferenceStats'
|
|
214
|
-
key: 'pardx:memory:preferenceStats:'
|
|
215
|
-
expireIn: 300 # 5分钟
|
|
216
|
-
- name: 'processTaskIdList' # 正在处理的taskId
|
|
217
|
-
key: 'pardx:processTaskList:taskId:'
|
|
218
|
-
expireIn: 86400
|
|
219
|
-
- name: 'taskFileList' # 正在处理的task里面剩余处理的fsIds
|
|
220
|
-
key: 'pardx:taskFileList:fsIds:'
|
|
221
|
-
expireIn: 86400
|
|
222
164
|
# ========== 业务实体缓存 (用于 @Cacheable 装饰器) ==========
|
|
223
165
|
- name: 'userInfo' # 用户信息缓存
|
|
224
166
|
key: 'pardx:cache:user:'
|
|
225
167
|
expireIn: 1800 # 30分钟,提升登录响应速度
|
|
226
|
-
- name: 'spaceInfo' # 空间信息缓存
|
|
227
|
-
key: 'pardx:cache:space:'
|
|
228
|
-
expireIn: 1800 # 30分钟
|
|
229
|
-
- name: 'planInfo' # 套餐信息缓存
|
|
230
|
-
key: 'pardx:cache:plan:'
|
|
231
|
-
expireIn: 300 # 5分钟,变更频率低,查询频繁
|
|
232
168
|
- name: 'permissions' # 权限配置缓存
|
|
233
169
|
key: 'pardx:cache:perm:'
|
|
234
170
|
expireIn: 600 # 10分钟,基础配置,变更少
|
|
235
171
|
- name: 'modulePermission' # 模块级权限缓存
|
|
236
172
|
key: 'pardx:cache:modulePerm:'
|
|
237
173
|
expireIn: 300 # 5分钟
|
|
238
|
-
- name: 'userRole' # 用户角色缓存 (包含 baseRole)
|
|
239
|
-
key: 'pardx:cache:userRole:'
|
|
240
|
-
expireIn: 3600 # 1小时
|
|
241
|
-
- name: 'userInfoCache' # 旧的用户缓存 (保持兼容)
|
|
242
|
-
key: 'pardx:user:'
|
|
243
|
-
expireIn: -1
|
|
244
174
|
- name: 'systemTask'
|
|
245
175
|
key: 'pardx:task:'
|
|
246
176
|
expireIn: -1
|
|
247
|
-
- name: 'stockTask'
|
|
248
|
-
key: 'pardx:stockTask:'
|
|
249
|
-
expireIn: 86400
|
|
250
|
-
- name: 'systemTaskListenerLock'
|
|
251
|
-
key: 'pardx:taskListener:'
|
|
252
|
-
expireIn: 15
|
|
253
|
-
- name: 'taskLock'
|
|
254
|
-
key: 'pardx:tasks:'
|
|
255
|
-
expireIn: 10
|
|
256
|
-
- name: 'tradeCache'
|
|
257
|
-
key: 'pardx:file:fileurl:'
|
|
258
|
-
expireIn: 30 #30s
|
|
259
177
|
- name: 'sseChannel'
|
|
260
178
|
key: 'pardx:sseChannel:'
|
|
261
179
|
expireIn: 86400
|
|
262
|
-
|
|
263
|
-
key: 'pardx:wx:accessToken:'
|
|
264
|
-
expireIn: 7200
|
|
265
|
-
- name: 'wxmpQrcode'
|
|
266
|
-
key: 'pardx:wxmp:qrcode:'
|
|
267
|
-
expireIn: 300
|
|
268
|
-
# ⚠️ P2: 流式识别会话数据持久化(Redis)
|
|
180
|
+
# 流式识别会话数据持久化(Redis)
|
|
269
181
|
- name: 'streamingAsrSession'
|
|
270
182
|
key: 'pardx:streaming-asr:session:'
|
|
271
183
|
expireIn: 7200 # 2小时(与会话最大时长一致)
|
|
@@ -23,7 +23,6 @@ export type {
|
|
|
23
23
|
MicroServiceConfig,
|
|
24
24
|
AppConfig,
|
|
25
25
|
ZoneConfig,
|
|
26
|
-
UploadConfig,
|
|
27
26
|
IpInfoConfig,
|
|
28
27
|
JwtConfig,
|
|
29
28
|
CryptoConfig,
|
|
@@ -83,7 +82,6 @@ import type {
|
|
|
83
82
|
MicroServiceConfig as MicroServiceConfigType,
|
|
84
83
|
AppConfig as AppConfigType,
|
|
85
84
|
ZoneConfig as ZoneConfigType,
|
|
86
|
-
UploadConfig as UploadConfigType,
|
|
87
85
|
IpInfoConfig as IpInfoConfigType,
|
|
88
86
|
JwtConfig as JwtConfigType,
|
|
89
87
|
CryptoConfig as CryptoConfigType,
|
|
@@ -195,9 +193,6 @@ export namespace PardxApp {
|
|
|
195
193
|
/** @deprecated 使用 ZoneConfig from '../validation' */
|
|
196
194
|
export type Zone = ZoneConfigType;
|
|
197
195
|
|
|
198
|
-
/** @deprecated 使用 UploadConfig from '../validation' */
|
|
199
|
-
export type Upload = UploadConfigType;
|
|
200
|
-
|
|
201
196
|
/** @deprecated 使用 TranscodeConfig from '../validation' */
|
|
202
197
|
export type Transcode = TranscodeConfigType;
|
|
203
198
|
|
|
@@ -35,7 +35,6 @@ export {
|
|
|
35
35
|
microServiceSchema,
|
|
36
36
|
appConfigSchema,
|
|
37
37
|
zoneSchema,
|
|
38
|
-
uploadConfigSchema,
|
|
39
38
|
ipInfoConfigSchema,
|
|
40
39
|
videoQualitySchema,
|
|
41
40
|
jwtConfigSchema,
|
|
@@ -77,7 +76,6 @@ export type {
|
|
|
77
76
|
MicroServiceConfig,
|
|
78
77
|
AppConfig,
|
|
79
78
|
ZoneConfig,
|
|
80
|
-
UploadConfig,
|
|
81
79
|
IpInfoConfig,
|
|
82
80
|
JwtConfig,
|
|
83
81
|
CryptoConfig,
|
|
@@ -43,46 +43,18 @@ export const appConfigSchema = microServiceSchema.extend({
|
|
|
43
43
|
MaxPageSize: z.number().int().positive().default(500),
|
|
44
44
|
defaultPageSize: z.number().int().positive().default(100),
|
|
45
45
|
defaultMiniPageSize: z.number().int().positive().default(30),
|
|
46
|
-
canCreateTrail: z.boolean().default(true),
|
|
47
46
|
defaultVendor: z
|
|
48
47
|
.enum(['tos', 'oss', 'us3', 'qiniu', 'gcs', 's3'])
|
|
49
48
|
.default('tos'),
|
|
50
49
|
defaultBucketPublic: z.boolean().default(false),
|
|
51
50
|
|
|
52
|
-
// S3 重试配置 (从 .env 迁移)
|
|
53
|
-
enableRetryMechanism: z.boolean().default(true),
|
|
54
|
-
enableEnhancedLogging: z.boolean().default(true),
|
|
55
|
-
maxRetries: z.number().int().positive().default(3),
|
|
56
|
-
baseRetryDelay: z.number().int().positive().default(1000),
|
|
57
|
-
|
|
58
51
|
// 日志输出配置 (从 .env 迁移)
|
|
59
52
|
nestLogOutput: z.enum(['console', 'file', 'both']).default('file'),
|
|
60
53
|
|
|
61
|
-
// 音频转写状态更新模式配置
|
|
62
|
-
audioTranscribe: z
|
|
63
|
-
.object({
|
|
64
|
-
/** 状态更新模式:webhook(回调)或 polling(轮询) */
|
|
65
|
-
statusUpdateMode: z.enum(['webhook', 'polling']).default('webhook'),
|
|
66
|
-
/** 轮询间隔(秒),仅在 polling 模式下有效 */
|
|
67
|
-
pollingInterval: z.number().int().positive().default(30),
|
|
68
|
-
})
|
|
69
|
-
.optional()
|
|
70
|
-
.default({
|
|
71
|
-
statusUpdateMode: 'webhook',
|
|
72
|
-
pollingInterval: 30,
|
|
73
|
-
}),
|
|
74
|
-
|
|
75
54
|
admin: microServiceSchema.optional(),
|
|
76
55
|
zones: z.array(zoneSchema).min(1),
|
|
77
56
|
});
|
|
78
57
|
|
|
79
|
-
/**
|
|
80
|
-
* Upload configuration schema
|
|
81
|
-
*/
|
|
82
|
-
export const uploadConfigSchema = z.object({
|
|
83
|
-
chrunkSize: z.number().int().positive().default(8388608), // 8MB
|
|
84
|
-
});
|
|
85
|
-
|
|
86
58
|
/**
|
|
87
59
|
* IP info configuration schema
|
|
88
60
|
*/
|
|
@@ -411,11 +383,9 @@ export const rateLimitConfigSchema = z.object({
|
|
|
411
383
|
*/
|
|
412
384
|
export const yamlConfigSchema = z.object({
|
|
413
385
|
app: appConfigSchema,
|
|
414
|
-
uploadConfig: uploadConfigSchema.optional(),
|
|
415
386
|
ipinfo: ipInfoConfigSchema.optional(),
|
|
416
387
|
outOfAnonymityPath: pathConfigSchema.optional(),
|
|
417
388
|
outOfUserPath: pathConfigSchema.optional(),
|
|
418
|
-
pinecone: z.object({ apiKey: z.string() }).optional(),
|
|
419
389
|
jwt: jwtConfigSchema,
|
|
420
390
|
crypto: cryptoConfigSchema,
|
|
421
391
|
cdn: cdnConfigSchema.optional(),
|
|
@@ -444,9 +414,6 @@ export type AppConfig = z.infer<typeof appConfigSchema>;
|
|
|
444
414
|
/** 区域配置类型 */
|
|
445
415
|
export type ZoneConfig = z.infer<typeof zoneSchema>;
|
|
446
416
|
|
|
447
|
-
/** 上传配置类型 */
|
|
448
|
-
export type UploadConfig = z.infer<typeof uploadConfigSchema>;
|
|
449
|
-
|
|
450
417
|
/** IP 信息配置类型 */
|
|
451
418
|
export type IpInfoConfig = z.infer<typeof ipInfoConfigSchema>;
|
|
452
419
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma 7.x Configuration File
|
|
3
|
+
*
|
|
4
|
+
* This file is used by Prisma CLI commands (migrate, db push, etc.)
|
|
5
|
+
* For PrismaClient initialization, the URL is passed via the driver adapter
|
|
6
|
+
* in PrismaWriteService and PrismaReadService.
|
|
7
|
+
*
|
|
8
|
+
* 注意: 生成器配置(如 output 路径)需要在 schema.prisma 中配置,
|
|
9
|
+
* 不在此文件中配置。
|
|
10
|
+
*
|
|
11
|
+
* @see https://www.prisma.io/docs/orm/more/upgrade-guides/upgrading-versions/upgrading-to-prisma-7
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as dotenv from 'dotenv';
|
|
15
|
+
import * as dotenvExpand from 'dotenv-expand';
|
|
16
|
+
import { defineConfig, env } from 'prisma/config';
|
|
17
|
+
|
|
18
|
+
// 使用 dotenv-expand 展开环境变量(如 ${BASE_HOST})
|
|
19
|
+
dotenvExpand.expand(dotenv.config());
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
schema: 'prisma/schema.prisma',
|
|
23
|
+
migrations: {
|
|
24
|
+
path: 'prisma/migrations',
|
|
25
|
+
},
|
|
26
|
+
datasource: {
|
|
27
|
+
url: env('DATABASE_URL'),
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// API 配置
|
|
2
2
|
// API 基础地址从环境变量读取
|
|
3
3
|
// 在 .env.local 文件中设置以下环境变量:
|
|
4
|
-
// -
|
|
4
|
+
// - NEXT_PUBLIC_SERVER_BASE_URL: 登录、上传文件、测评等接口的基础地址
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* 获取
|
|
7
|
+
* 获取 Server 基础地址(用于登录、上传文件、测评等)
|
|
8
8
|
*/
|
|
9
|
-
const
|
|
10
|
-
const baseUrl = process.env.
|
|
9
|
+
const getServerBaseUrl = (): string => {
|
|
10
|
+
const baseUrl = process.env.NEXT_PUBLIC_SERVER_BASE_URL;
|
|
11
11
|
|
|
12
12
|
if (!baseUrl) {
|
|
13
13
|
// Use default value during build time or development
|
|
14
|
-
const defaultUrl = 'http://localhost:3100
|
|
14
|
+
const defaultUrl = 'http://localhost:3100';
|
|
15
15
|
if (
|
|
16
16
|
process.env.NODE_ENV === 'development' ||
|
|
17
17
|
process.env.NEXT_PHASE === 'phase-production-build'
|
|
@@ -19,7 +19,7 @@ const getApiBaseUrl = (): string => {
|
|
|
19
19
|
return defaultUrl;
|
|
20
20
|
}
|
|
21
21
|
// At runtime in production, use default if not set
|
|
22
|
-
console.warn('
|
|
22
|
+
console.warn('NEXT_PUBLIC_SERVER_BASE_URL 未设置,使用默认值', defaultUrl);
|
|
23
23
|
return defaultUrl;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -78,7 +78,8 @@ const getBrandDescription = (): string => {
|
|
|
78
78
|
|
|
79
79
|
export const API_CONFIG = {
|
|
80
80
|
// API 基础地址(用于登录、上传文件、测评等)
|
|
81
|
-
baseUrl:
|
|
81
|
+
baseUrl: getServerBaseUrl() + '/api',
|
|
82
|
+
apiHealthUrl: getServerBaseUrl() + '/health',
|
|
82
83
|
|
|
83
84
|
// API 端点路径 这些断点是不适用ts-rest-api的,用于登录和校验权限的断点,如果未来需要使用ts-rest-api,则将这些断点迁移到ts-rest-api中
|
|
84
85
|
endpoints: {
|
|
@@ -29,8 +29,9 @@ import { useState, useEffect, useRef, useCallback } from 'react';
|
|
|
29
29
|
import { useQueryClient } from '@tanstack/react-query';
|
|
30
30
|
import { /*getToken,*/ ensureValidToken, isTokenExpired } from '@/lib/api';
|
|
31
31
|
import { notificationKeys } from '@/lib/api/contracts/hooks/notification';
|
|
32
|
+
import { API_CONFIG } from '@/config';
|
|
32
33
|
|
|
33
|
-
const API_BASE_URL =
|
|
34
|
+
const API_BASE_URL = API_CONFIG.baseUrl;
|
|
34
35
|
|
|
35
36
|
interface SSEMessage {
|
|
36
37
|
t: 'm' | 'p'; // 'm' = message, 'p' = ping
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { useEffect, useRef, useCallback, useState } from 'react';
|
|
28
|
+
import { API_CONFIG } from '@/config';
|
|
28
29
|
|
|
29
30
|
// ============================================================================
|
|
30
31
|
// Types
|
|
@@ -93,7 +94,7 @@ export function useVersionCheck(options: UseVersionCheckOptions = {}) {
|
|
|
93
94
|
checkInterval = 60000,
|
|
94
95
|
autoRefresh = false,
|
|
95
96
|
onVersionMismatch,
|
|
96
|
-
apiBaseUrl =
|
|
97
|
+
apiBaseUrl = API_CONFIG.baseUrl,
|
|
97
98
|
enabled = true,
|
|
98
99
|
} = options;
|
|
99
100
|
|
|
@@ -20,7 +20,6 @@ import type zhCNSettings from '../locales/zh-CN/settings.json';
|
|
|
20
20
|
import type zhCNSubscription from '../locales/zh-CN/subscription.json';
|
|
21
21
|
import type zhCNRecommendation from '../locales/zh-CN/recommendation.json';
|
|
22
22
|
import type zhCNMemory from '../locales/zh-CN/memory.json';
|
|
23
|
-
import type zhCNDailyChallenge from '../locales/zh-CN/daily-challenge.json';
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* 所有翻译消息的类型定义
|
|
@@ -39,7 +38,6 @@ export interface AppMessages {
|
|
|
39
38
|
subscription: typeof zhCNSubscription;
|
|
40
39
|
recommendation: typeof zhCNRecommendation;
|
|
41
40
|
memory: typeof zhCNMemory;
|
|
42
|
-
'daily-challenge': typeof zhCNDailyChallenge;
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
/**
|
|
@@ -49,7 +47,6 @@ export interface AppMessages {
|
|
|
49
47
|
declare global {
|
|
50
48
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
51
49
|
namespace IntlMessages {
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
53
50
|
interface Messages extends AppMessages {}
|
|
54
51
|
}
|
|
55
52
|
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { useEffect } from 'react';
|
|
24
24
|
import { usePathname, useSearchParams } from 'next/navigation';
|
|
25
25
|
import { analytics } from '../index';
|
|
26
|
+
import type { EventProperties } from '@repo/contracts/schemas/analytics.schema';
|
|
26
27
|
|
|
27
28
|
interface PageTrackerProps {
|
|
28
29
|
/**
|
|
@@ -56,9 +57,10 @@ export function PageTracker({
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// Build full path with search params if enabled
|
|
59
|
-
const fullPath =
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const fullPath =
|
|
61
|
+
trackSearchParams && searchParams.toString()
|
|
62
|
+
? `${pathname}?${searchParams.toString()}`
|
|
63
|
+
: pathname;
|
|
62
64
|
|
|
63
65
|
// Get page name (custom or pathname)
|
|
64
66
|
const pageName = pageNameMapper ? pageNameMapper(pathname) : pathname;
|
|
@@ -67,10 +69,13 @@ export function PageTracker({
|
|
|
67
69
|
analytics.pageView(fullPath, pageName);
|
|
68
70
|
|
|
69
71
|
// Track session start on first page view
|
|
70
|
-
if (
|
|
72
|
+
if (
|
|
73
|
+
typeof window !== 'undefined' &&
|
|
74
|
+
!sessionStorage.getItem('session_started')
|
|
75
|
+
) {
|
|
71
76
|
analytics.track('SESSION_START', {
|
|
72
77
|
path: fullPath,
|
|
73
|
-
} as
|
|
78
|
+
} as unknown as EventProperties);
|
|
74
79
|
sessionStorage.setItem('session_started', 'true');
|
|
75
80
|
}
|
|
76
81
|
}, [pathname, searchParams, trackSearchParams, pageNameMapper, excludePaths]);
|
|
@@ -80,7 +85,7 @@ export function PageTracker({
|
|
|
80
85
|
const handleBeforeUnload = () => {
|
|
81
86
|
analytics.track('SESSION_END', {
|
|
82
87
|
path: pathname,
|
|
83
|
-
} as
|
|
88
|
+
} as unknown as EventProperties);
|
|
84
89
|
};
|
|
85
90
|
|
|
86
91
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
@@ -96,11 +101,11 @@ export function PageTracker({
|
|
|
96
101
|
if (document.hidden) {
|
|
97
102
|
analytics.track('APP_BACKGROUND', {
|
|
98
103
|
path: pathname,
|
|
99
|
-
} as
|
|
104
|
+
} as unknown as EventProperties);
|
|
100
105
|
} else {
|
|
101
106
|
analytics.track('APP_FOREGROUND', {
|
|
102
107
|
path: pathname,
|
|
103
|
-
} as
|
|
108
|
+
} as unknown as EventProperties);
|
|
104
109
|
}
|
|
105
110
|
};
|
|
106
111
|
|
|
@@ -121,9 +126,6 @@ export const defaultPageNameMapper = (pathname: string): string => {
|
|
|
121
126
|
const routes: Record<string, string> = {
|
|
122
127
|
'/': 'Home',
|
|
123
128
|
'/home': 'Home',
|
|
124
|
-
'/daily-challenge': 'Daily Challenge',
|
|
125
|
-
'/daily-challenge/result': 'Challenge Result',
|
|
126
|
-
'/daily-challenge/history': 'Challenge History',
|
|
127
129
|
'/settings': 'Settings',
|
|
128
130
|
'/login': 'Login',
|
|
129
131
|
'/register': 'Register',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
7
|
+
import { API_CONFIG } from '@/config';
|
|
7
8
|
|
|
8
9
|
export interface PythonTaskResponse {
|
|
9
10
|
taskId: string;
|
|
@@ -78,7 +79,7 @@ export function usePythonTask({
|
|
|
78
79
|
useEffect(() => {
|
|
79
80
|
if (!taskId) return;
|
|
80
81
|
|
|
81
|
-
const apiBase =
|
|
82
|
+
const apiBase = API_CONFIG.baseUrl;
|
|
82
83
|
const eventSource = new EventSource(
|
|
83
84
|
`${apiBase}/python-tasks/${taskId}/stream`,
|
|
84
85
|
);
|
|
@@ -32,7 +32,7 @@ export default async function middleware(request: NextRequest) {
|
|
|
32
32
|
const pathWithoutLocale = pathname.replace(/^\/(zh-CN|en)/, '') || '/';
|
|
33
33
|
|
|
34
34
|
// Skip daily check-in logic for public routes
|
|
35
|
-
const publicRoutes = ['/
|
|
35
|
+
const publicRoutes = ['/login', '/register'];
|
|
36
36
|
if (publicRoutes.some((route) => pathWithoutLocale.includes(route))) {
|
|
37
37
|
return intlResponse;
|
|
38
38
|
}
|
|
@@ -43,46 +43,6 @@ export default async function middleware(request: NextRequest) {
|
|
|
43
43
|
return intlResponse;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Check today's check-in status
|
|
47
|
-
try {
|
|
48
|
-
const apiUrl =
|
|
49
|
-
process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3100/api';
|
|
50
|
-
const response = await fetch(`${apiUrl}/daily-question/streak`, {
|
|
51
|
-
headers: {
|
|
52
|
-
Authorization: `Bearer ${token}`,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
if (response.ok) {
|
|
57
|
-
const data = await response.json();
|
|
58
|
-
const streak = data.data;
|
|
59
|
-
|
|
60
|
-
if (streak?.lastCheckInDate) {
|
|
61
|
-
const today = new Date();
|
|
62
|
-
today.setHours(0, 0, 0, 0);
|
|
63
|
-
const lastCheckIn = new Date(streak.lastCheckInDate);
|
|
64
|
-
lastCheckIn.setHours(0, 0, 0, 0);
|
|
65
|
-
|
|
66
|
-
// If not checked in today, redirect to daily challenge
|
|
67
|
-
if (lastCheckIn.getTime() !== today.getTime()) {
|
|
68
|
-
const url = request.nextUrl.clone();
|
|
69
|
-
// 保留语言前缀
|
|
70
|
-
const locale = pathname.match(/^\/(zh-CN|en)/)?.[1] || 'zh-CN';
|
|
71
|
-
url.pathname = `/${locale}/daily-challenge`;
|
|
72
|
-
return NextResponse.redirect(url);
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
// No check-in history, redirect to daily challenge
|
|
76
|
-
const url = request.nextUrl.clone();
|
|
77
|
-
const locale = pathname.match(/^\/(zh-CN|en)/)?.[1] || 'zh-CN';
|
|
78
|
-
url.pathname = `/${locale}/daily-challenge`;
|
|
79
|
-
return NextResponse.redirect(url);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error('Middleware check-in status error:', error);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
46
|
return intlResponse;
|
|
87
47
|
}
|
|
88
48
|
|
|
@@ -43,14 +43,6 @@ export default {
|
|
|
43
43
|
);
|
|
44
44
|
},
|
|
45
45
|
|
|
46
|
-
escapeMongoRegexSpecialChars(inputString: string): string {
|
|
47
|
-
// MongoDB中的正则表达式特殊字符
|
|
48
|
-
const mongoRegexSpecialChars = /[\.^$*{[\]|}\\+?\-()]/g;
|
|
49
|
-
|
|
50
|
-
// 使用replace函数和回调函数来替换特殊字符
|
|
51
|
-
return inputString.replace(mongoRegexSpecialChars, (match) => `\\${match}`);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
46
|
generateString(input: string, suffixLength = 3): string {
|
|
55
47
|
// 判断是否为中文
|
|
56
48
|
const isChinese = /^[\u4e00-\u9fa5]+$/.test(input);
|
|
@@ -25,7 +25,8 @@ const log = {
|
|
|
25
25
|
success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
|
|
26
26
|
warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
|
|
27
27
|
error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
|
|
28
|
-
header: (msg) =>
|
|
28
|
+
header: (msg) =>
|
|
29
|
+
console.log(`\n${colors.bright}${colors.cyan}${msg}${colors.reset}\n`),
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
// Create readline interface
|
|
@@ -35,7 +36,8 @@ const rl = readline.createInterface({
|
|
|
35
36
|
});
|
|
36
37
|
|
|
37
38
|
// Promisify question
|
|
38
|
-
const question = (query) =>
|
|
39
|
+
const question = (query) =>
|
|
40
|
+
new Promise((resolve) => rl.question(query, resolve));
|
|
39
41
|
|
|
40
42
|
// Project configuration
|
|
41
43
|
// 注意:API 端口在 apps/api/config.local.yaml 的 app.port 配置;Web 端口由 Next.js 自动配置,此处不配置
|
|
@@ -62,49 +64,53 @@ async function main() {
|
|
|
62
64
|
try {
|
|
63
65
|
// Collect project information
|
|
64
66
|
config.projectName = await question(
|
|
65
|
-
`${colors.cyan}Project name${colors.reset} (e.g., my-awesome-app):
|
|
67
|
+
`${colors.cyan}Project name${colors.reset} (e.g., my-awesome-app): `,
|
|
66
68
|
);
|
|
67
69
|
config.projectName = config.projectName.trim() || 'my-project';
|
|
68
70
|
|
|
69
71
|
config.projectDescription = await question(
|
|
70
|
-
`${colors.cyan}Project description${colors.reset}:
|
|
72
|
+
`${colors.cyan}Project description${colors.reset}: `,
|
|
71
73
|
);
|
|
72
|
-
config.projectDescription =
|
|
74
|
+
config.projectDescription =
|
|
75
|
+
config.projectDescription.trim() || 'A PardxAI monorepo project';
|
|
73
76
|
|
|
74
77
|
config.authorName = await question(
|
|
75
|
-
`${colors.cyan}Author name${colors.reset}:
|
|
78
|
+
`${colors.cyan}Author name${colors.reset}: `,
|
|
76
79
|
);
|
|
77
80
|
config.authorName = config.authorName.trim() || 'Your Name';
|
|
78
81
|
|
|
79
82
|
config.authorEmail = await question(
|
|
80
|
-
`${colors.cyan}Author email${colors.reset}:
|
|
83
|
+
`${colors.cyan}Author email${colors.reset}: `,
|
|
81
84
|
);
|
|
82
85
|
config.authorEmail = config.authorEmail.trim() || 'your.email@example.com';
|
|
83
86
|
|
|
84
87
|
log.header('\n📦 Configuration');
|
|
85
88
|
|
|
86
89
|
config.databaseUrl = await question(
|
|
87
|
-
`${colors.cyan}Database URL${colors.reset} [postgresql://user:password@localhost:5432/dbname]:
|
|
90
|
+
`${colors.cyan}Database URL${colors.reset} [postgresql://user:password@localhost:5432/dbname]: `,
|
|
88
91
|
);
|
|
89
|
-
config.databaseUrl =
|
|
92
|
+
config.databaseUrl =
|
|
93
|
+
config.databaseUrl.trim() ||
|
|
94
|
+
'postgresql://user:password@localhost:5432/dbname';
|
|
90
95
|
|
|
91
96
|
config.readDatabaseUrl = await question(
|
|
92
|
-
`${colors.cyan}Read Database URL${colors.reset} [same as Database URL]:
|
|
97
|
+
`${colors.cyan}Read Database URL${colors.reset} [same as Database URL]: `,
|
|
93
98
|
);
|
|
94
|
-
config.readDatabaseUrl =
|
|
99
|
+
config.readDatabaseUrl =
|
|
100
|
+
config.readDatabaseUrl.trim() || config.databaseUrl;
|
|
95
101
|
|
|
96
102
|
config.redisUrl = await question(
|
|
97
|
-
`${colors.cyan}Redis URL${colors.reset} [redis://localhost:6379]:
|
|
103
|
+
`${colors.cyan}Redis URL${colors.reset} [redis://localhost:6379]: `,
|
|
98
104
|
);
|
|
99
105
|
config.redisUrl = config.redisUrl.trim() || 'redis://localhost:6379';
|
|
100
106
|
|
|
101
107
|
config.rabbitmqUrl = await question(
|
|
102
|
-
`${colors.cyan}RabbitMQ URL${colors.reset} [amqp://localhost:5672]:
|
|
108
|
+
`${colors.cyan}RabbitMQ URL${colors.reset} [amqp://localhost:5672]: `,
|
|
103
109
|
);
|
|
104
110
|
config.rabbitmqUrl = config.rabbitmqUrl.trim() || 'amqp://localhost:5672';
|
|
105
111
|
|
|
106
112
|
config.baseHost = await question(
|
|
107
|
-
`${colors.cyan}Base Host${colors.reset} [127.0.0.1]:
|
|
113
|
+
`${colors.cyan}Base Host${colors.reset} [127.0.0.1]: `,
|
|
108
114
|
);
|
|
109
115
|
config.baseHost = config.baseHost.trim() || '127.0.0.1';
|
|
110
116
|
|
|
@@ -112,9 +118,13 @@ async function main() {
|
|
|
112
118
|
|
|
113
119
|
// Display configuration summary
|
|
114
120
|
log.header('\n📋 Configuration Summary');
|
|
115
|
-
console.log(
|
|
121
|
+
console.log(
|
|
122
|
+
` Project Name: ${colors.green}${config.projectName}${colors.reset}`,
|
|
123
|
+
);
|
|
116
124
|
console.log(` Description: ${config.projectDescription}`);
|
|
117
|
-
console.log(
|
|
125
|
+
console.log(
|
|
126
|
+
` Author: ${config.authorName} <${config.authorEmail}>`,
|
|
127
|
+
);
|
|
118
128
|
console.log(` Base Host: ${config.baseHost}`);
|
|
119
129
|
console.log(` Database: ${config.databaseUrl}`);
|
|
120
130
|
console.log(` Read DB: ${config.readDatabaseUrl}`);
|
|
@@ -128,12 +138,19 @@ async function main() {
|
|
|
128
138
|
// Success message
|
|
129
139
|
log.header('\n✨ Project Initialized Successfully!');
|
|
130
140
|
log.info('Next steps:');
|
|
131
|
-
console.log(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
console.log(
|
|
141
|
+
console.log(
|
|
142
|
+
` 1. ${colors.cyan}pnpm install${colors.reset} - Install dependencies`,
|
|
143
|
+
);
|
|
144
|
+
console.log(
|
|
145
|
+
` 2. ${colors.cyan}pnpm db:generate${colors.reset} - Generate Prisma client`,
|
|
146
|
+
);
|
|
147
|
+
console.log(
|
|
148
|
+
` 3. ${colors.cyan}pnpm db:migrate:dev${colors.reset} - Run database migrations`,
|
|
149
|
+
);
|
|
150
|
+
console.log(
|
|
151
|
+
` 4. ${colors.cyan}pnpm dev${colors.reset} - Start development servers`,
|
|
152
|
+
);
|
|
135
153
|
console.log('');
|
|
136
|
-
|
|
137
154
|
} catch (error) {
|
|
138
155
|
log.error(`Initialization failed: ${error.message}`);
|
|
139
156
|
process.exit(1);
|
|
@@ -248,7 +265,8 @@ function createEnvFromExample(rootDir, config) {
|
|
|
248
265
|
} else if (val === undefined) {
|
|
249
266
|
val = null;
|
|
250
267
|
}
|
|
251
|
-
const final =
|
|
268
|
+
const final =
|
|
269
|
+
val !== null && val !== undefined ? `${item.key}=${val}` : item.raw;
|
|
252
270
|
outLines.push(final);
|
|
253
271
|
}
|
|
254
272
|
}
|
|
@@ -258,7 +276,10 @@ function createEnvFromExample(rootDir, config) {
|
|
|
258
276
|
// 追加 JWT_SECRET(若 .env.example 中无)
|
|
259
277
|
const apiEnvContent = fs.readFileSync(apiEnvPath, 'utf8');
|
|
260
278
|
if (!/^JWT_SECRET=/m.test(apiEnvContent)) {
|
|
261
|
-
fs.appendFileSync(
|
|
279
|
+
fs.appendFileSync(
|
|
280
|
+
apiEnvPath,
|
|
281
|
+
`\n# JWT\nJWT_SECRET=${generateRandomSecret()}\nJWT_EXPIRES_IN=7d\n`,
|
|
282
|
+
);
|
|
262
283
|
}
|
|
263
284
|
} else {
|
|
264
285
|
createEnvFile(path.join(rootDir, 'apps/api/.env'), {
|
|
@@ -270,7 +291,6 @@ function createEnvFromExample(rootDir, config) {
|
|
|
270
291
|
});
|
|
271
292
|
}
|
|
272
293
|
|
|
273
|
-
// apps/web:NEXT_PUBLIC_API_BASE_URL 等由 .env.example 提供,Next.js 端口自动配置,此处不覆盖
|
|
274
294
|
const webReplacements = {};
|
|
275
295
|
|
|
276
296
|
if (fs.existsSync(webExamplePath)) {
|
|
@@ -288,7 +308,7 @@ function createEnvFromExample(rootDir, config) {
|
|
|
288
308
|
}
|
|
289
309
|
fs.writeFileSync(
|
|
290
310
|
path.join(rootDir, 'apps/web/.env.local'),
|
|
291
|
-
outLines.join('\n') + '\n'
|
|
311
|
+
outLines.join('\n') + '\n',
|
|
292
312
|
);
|
|
293
313
|
} else {
|
|
294
314
|
createEnvFile(path.join(rootDir, 'apps/web/.env.local'), {});
|
|
@@ -296,9 +316,10 @@ function createEnvFromExample(rootDir, config) {
|
|
|
296
316
|
}
|
|
297
317
|
|
|
298
318
|
function createEnvFile(filePath, variables) {
|
|
299
|
-
const content =
|
|
300
|
-
.
|
|
301
|
-
|
|
319
|
+
const content =
|
|
320
|
+
Object.entries(variables)
|
|
321
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
322
|
+
.join('\n') + '\n';
|
|
302
323
|
fs.writeFileSync(filePath, content);
|
|
303
324
|
}
|
|
304
325
|
|
|
@@ -309,10 +330,13 @@ function updateReadme(filePath) {
|
|
|
309
330
|
}
|
|
310
331
|
|
|
311
332
|
let content = fs.readFileSync(filePath, 'utf8');
|
|
312
|
-
content = content.replace(
|
|
333
|
+
content = content.replace(
|
|
334
|
+
/# PardxAI Monorepo Scaffold[^\n]*/,
|
|
335
|
+
`# ${config.projectName}`,
|
|
336
|
+
);
|
|
313
337
|
content = content.replace(
|
|
314
338
|
/A comprehensive production-ready monorepo scaffold with complete implementations\./,
|
|
315
|
-
config.projectDescription
|
|
339
|
+
config.projectDescription,
|
|
316
340
|
);
|
|
317
341
|
fs.writeFileSync(filePath, content);
|
|
318
342
|
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
// API 配置
|
|
2
|
-
// API 基础地址从环境变量读取
|
|
3
|
-
// 在 .env.local 文件中设置以下环境变量:
|
|
4
|
-
// - NEXT_PUBLIC_API_BASE_URL: 登录、上传文件、测评等接口的基础地址
|
|
5
|
-
// - NEXT_PUBLIC_AGUI_API_BASE_URL: Agno 相关接口的基础地址
|
|
6
|
-
// - NEXT_PUBLIC_AGUI_API_BASE_URL: AgUI 相关接口的基础地址
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 获取 API 基础地址(用于登录、上传文件、测评等)
|
|
10
|
-
*/
|
|
11
|
-
const getApiBaseUrl = (): string => {
|
|
12
|
-
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
|
|
13
|
-
|
|
14
|
-
if (!baseUrl) {
|
|
15
|
-
// Use default value during build time or development
|
|
16
|
-
const defaultUrl = 'http://localhost:3100/api';
|
|
17
|
-
if (
|
|
18
|
-
process.env.NODE_ENV === 'development' ||
|
|
19
|
-
process.env.NEXT_PHASE === 'phase-production-build'
|
|
20
|
-
) {
|
|
21
|
-
return defaultUrl;
|
|
22
|
-
}
|
|
23
|
-
// At runtime in production, use default if not set
|
|
24
|
-
console.warn('NEXT_PUBLIC_API_BASE_URL 未设置,使用默认值', defaultUrl);
|
|
25
|
-
return defaultUrl;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return baseUrl;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 获取 Agno API 基础地址
|
|
33
|
-
*/
|
|
34
|
-
const getAgnoApiBaseUrl = (): string => {
|
|
35
|
-
const baseUrl = process.env.NEXT_PUBLIC_AGUI_API_BASE_URL;
|
|
36
|
-
|
|
37
|
-
if (!baseUrl) {
|
|
38
|
-
// Use default value during build time or development
|
|
39
|
-
const defaultUrl = 'http://127.0.0.1:8000/api';
|
|
40
|
-
// At runtime in production, use default if not set
|
|
41
|
-
console.warn(
|
|
42
|
-
'NEXT_PUBLIC_AGUI_API_BASE_URL 未设置,使用默认值',
|
|
43
|
-
defaultUrl,
|
|
44
|
-
);
|
|
45
|
-
return defaultUrl;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return baseUrl;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 获取 AgUI 基础地址
|
|
53
|
-
*/
|
|
54
|
-
const getAguiBaseUrl = (): string => {
|
|
55
|
-
const baseUrl = process.env.NEXT_PUBLIC_AGUI_API_BASE_URL;
|
|
56
|
-
|
|
57
|
-
if (!baseUrl) {
|
|
58
|
-
// Use default value during build time or development
|
|
59
|
-
const defaultUrl = 'http://127.0.0.1:8000/api/agui';
|
|
60
|
-
// At runtime in production, use default if not set
|
|
61
|
-
console.warn(
|
|
62
|
-
'NEXT_PUBLIC_AGUI_API_BASE_URL 未设置,使用默认值',
|
|
63
|
-
defaultUrl,
|
|
64
|
-
);
|
|
65
|
-
return defaultUrl;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return baseUrl + '/agui';
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 获取品牌名称
|
|
73
|
-
*/
|
|
74
|
-
const getBrandName = (): string => {
|
|
75
|
-
const brandName = process.env.NEXT_PUBLIC_BRAND_NAME;
|
|
76
|
-
return brandName || 'Pardx.AI';
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 获取品牌 Logo 路径
|
|
81
|
-
*/
|
|
82
|
-
const getBrandLogo = (): string => {
|
|
83
|
-
const brandLogo = process.env.NEXT_PUBLIC_BRAND_LOGO;
|
|
84
|
-
return brandLogo || '/logo.svg';
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 获取品牌完整标题(用于页面标题)
|
|
89
|
-
*
|
|
90
|
-
* 产品定位:多智能体驱动的内容创作与运营平台
|
|
91
|
-
* 核心能力:
|
|
92
|
-
* - AI 内容创作(智能写作、创意生成)
|
|
93
|
-
* - 知识库管理(进化型知识库、智能提取、质量评估)
|
|
94
|
-
* - 智能推荐(向量检索、协同过滤)
|
|
95
|
-
* - 招聘面试(AI 招聘 Agent、简历解析、智能匹配)
|
|
96
|
-
* - 会议管理(实时转写、知识提取、纪要生成)
|
|
97
|
-
* - 多智能体协作(AG-UI/Agno)
|
|
98
|
-
*/
|
|
99
|
-
const getBrandTitle = (): string => {
|
|
100
|
-
const brandTitle = process.env.NEXT_PUBLIC_BRAND_TITLE;
|
|
101
|
-
return brandTitle || 'PardxAI - Multi-Agent Content Creation ';
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* 获取品牌描述(用于页面描述)
|
|
106
|
-
*
|
|
107
|
-
* 描述应包含:
|
|
108
|
-
* - 产品定位(多智能体平台)
|
|
109
|
-
* - 核心功能(内容创作、知识管理、智能推荐、招聘面试)
|
|
110
|
-
* - 技术优势(AI 驱动、多智能体协作)
|
|
111
|
-
* - 目标用户(企业团队、内容创作者、HR 团队)
|
|
112
|
-
*/
|
|
113
|
-
const getBrandDescription = (): string => {
|
|
114
|
-
const brandDescription = process.env.NEXT_PUBLIC_BRAND_DESCRIPTION;
|
|
115
|
-
return (
|
|
116
|
-
brandDescription ||
|
|
117
|
-
'PardxAI is an AI-powered multi-agent platform for content creation.'
|
|
118
|
-
);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export const API_CONFIG = {
|
|
122
|
-
// API 基础地址(用于登录、上传文件、测评等)
|
|
123
|
-
baseUrl: getApiBaseUrl(),
|
|
124
|
-
|
|
125
|
-
// Agno API 基础地址
|
|
126
|
-
agnoApiBaseUrl: getAgnoApiBaseUrl(),
|
|
127
|
-
|
|
128
|
-
// AgUI 基础地址
|
|
129
|
-
aguiBaseUrl: getAguiBaseUrl(),
|
|
130
|
-
|
|
131
|
-
// API 端点路径 这些断点是不适用ts-rest-api的,用于登录和校验权限的断点,如果未来需要使用ts-rest-api,则将这些断点迁移到ts-rest-api中
|
|
132
|
-
endpoints: {
|
|
133
|
-
// 登录端点
|
|
134
|
-
login: '/sign/in/mobile/password',
|
|
135
|
-
|
|
136
|
-
// Token 刷新端点
|
|
137
|
-
refreshToken: '/sign/refresh/token',
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* 品牌配置
|
|
143
|
-
* 可通过环境变量覆盖:
|
|
144
|
-
* - NEXT_PUBLIC_BRAND_NAME: 品牌名称(默认: "Pardx.AI")
|
|
145
|
-
* - NEXT_PUBLIC_BRAND_LOGO: Logo 路径(默认: "/logo.svg")
|
|
146
|
-
* - NEXT_PUBLIC_BRAND_TITLE: 页面标题(默认: "PardxAI - Multi-Agent Content Creation")
|
|
147
|
-
* - NEXT_PUBLIC_BRAND_DESCRIPTION: 页面描述(默认: "PardxAI is an AI-powered multi-agent platform for content creation.")
|
|
148
|
-
*
|
|
149
|
-
* 产品定位说明:
|
|
150
|
-
* PardxAI 是一个多智能体驱动的内容创作与运营平台,提供以下核心能力:
|
|
151
|
-
* 1. AI 内容创作:智能写作、创意生成、多模态内容创作
|
|
152
|
-
* 2. 知识库管理:进化型知识库系统、智能知识提取、质量评估、版本控制、相似度检测与合并
|
|
153
|
-
* 3. 智能推荐:基于向量检索和协同过滤的知识推荐系统
|
|
154
|
-
* 4. 招聘面试:AI 招聘 Agent、简历解析、JD 分析、人才匹配、智能面试
|
|
155
|
-
* 5. 会议管理:实时转写、知识提取、智能纪要生成
|
|
156
|
-
* 6. 多智能体协作:AG-UI/Agno 集成,支持多智能体协同工作
|
|
157
|
-
*/
|
|
158
|
-
export const BRAND_CONFIG = {
|
|
159
|
-
name: getBrandName(),
|
|
160
|
-
logo: getBrandLogo(),
|
|
161
|
-
title: getBrandTitle(),
|
|
162
|
-
description: getBrandDescription(),
|
|
163
|
-
};
|