Skip to content

Commit

Permalink
v3.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
vastxie committed Jul 31, 2024
1 parent dd0e1da commit c831009
Show file tree
Hide file tree
Showing 366 changed files with 1,878 additions and 1,537 deletions.
3 changes: 3 additions & 0 deletions .env.docker
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ ISDEV=FALSE
weChatOpenUrl=https://open.weixin.qq.com
weChatApiUrl=https://api.weixin.qq.com
weChatMpUrl=https://mp.weixin.qq.com

# 自定义后台路径
ADMIN_SERVE_ROOT=/admin
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ ISDEV=FALSE
weChatOpenUrl=https://open.weixin.qq.com
weChatApiUrl=https://api.weixin.qq.com
weChatMpUrl=https://mp.weixin.qq.com

# 自定义后台路径
ADMIN_SERVE_ROOT=/admin
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
node_modules
logs
pnpm-lock.yaml
sql
data
.idea
.env
/public/file
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"Luma"
]
}
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,41 @@

## 更新日志

### 稳定版 v3.7.0

- 新增代码预览~~及编辑~~弹窗(HTML 格式)。
- 新增问题编辑及重新生成功能。
- 新增全模型文件分析功能 (模型设置-文件上传类型),选择后对于非图片的文本格式,将读取文件内容,作为 system 传给 AI,图片按 4o 格式传图。(建议搭配 token 关联计费及 `gpt-4o-mini` 使用)
- 新增 Luma 视频图生视频,视频尺寸选择。
- 取消注册验证码,改为获取 邮箱/手机 验证码时滑动验证。
- 优化新建对话、对话中断、切换对话的逻辑。取消旧的初始新建对话页,可直接提问,或使用预设。AI回复中,禁止切换对话和开启新对话。
- 对话页应用广场改为弹窗方式。
- 调整暗色模式 UI 显示,整体显示更美观。
- 调整购买套餐弹窗样式。
- 后台显示设置新增 `显示全局水印` 选项,开启后将在对话页显示基于用户ID的水印。
- 后台插件显示-基础显示,新增 `隐藏插件` 选项,开启后,用户端将隐藏插件功能。
- 环境变量新增自定义后台路径设置,后台地址可自行使用 `/admin` 之外的的路径。
- 新增自定义欢迎页,跳转样式参考:
```html
<button class="button" onclick="goToChat()">开始对话</button>
<script>
function goToChat() {
window.parent.postMessage('goToChat', '*');
}
</script>
```
- ~~后台新增本地存储(开启后将优先使用本地存储方式保存数据,有些场景需开启跨域访问,可能需额外自行解决读写权限问题。文件存储目录为 `/public/file`,更新迁移时请做好数据维护及备份。)~~
- ~~开启本地存储后,生成的视频及音乐将默认保存到本地。~~
- ~~新增知识库问答【数据管理】-【内容预设】。通过检测提问关键词,将匹配到的内容附加到 `system` 参数中。~~
- ~~Midjourney 绘图适配 `describe` ,支持以图生文。~~
- ~~Suno 音乐新增调整歌词,选择音乐类型及风格。~~

注:划线部分为开发版保留功能。

<details>

<summary>历史日志</summary>

### 稳定版 v3.5.0

主要更新内容:
Expand All @@ -123,10 +158,6 @@
- 优化首页“九宫格”显示(使用内置对话预设、不再依赖后端配置)
- Midjourney 绘图支持 `图生图``人脸一致性``风格一致性``MJ版本`自定义参数等自定义选择。

<details>

<summary>历史日志</summary>

### 稳定版 v3.3.0

- 重构流式回复逻辑,加入错误反馈并优化用户端等待动画。
Expand Down
12 changes: 10 additions & 2 deletions dist/app.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,17 @@ AppModule = __decorate([
database_module_1.DatabaseModule,
serve_static_1.ServeStaticModule.forRoot({
rootPath: (0, path_1.join)(__dirname, '..', 'public/admin'),
serveRoot: '/admin',
serveRoot: process.env.ADMIN_SERVE_ROOT || '/admin',
}, {
rootPath: (0, path_1.join)(__dirname, '..', 'public'),
rootPath: (0, path_1.join)(__dirname, '..', 'public/file'),
serveRoot: '/file',
serveStaticOptions: {
setHeaders: (res, path, stat) => {
res.set('Access-Control-Allow-Origin', '*');
},
},
}, {
rootPath: (0, path_1.join)(__dirname, '..', 'public/chat'),
serveRoot: '/',
}),
user_module_1.UserModule,
Expand Down
27 changes: 19 additions & 8 deletions dist/common/filters/allExceptions.filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,31 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AllExceptionsFilter = void 0;
const common_1 = require("@nestjs/common");
const result_1 = require("../result");
const common_1 = require("@nestjs/common");
let AllExceptionsFilter = class AllExceptionsFilter {
catch(exception, host) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const exceptionRes = exception.getResponse() || 'inter server error';
const message = (exceptionRes === null || exceptionRes === void 0 ? void 0 : exceptionRes.message) ? (Array.isArray(exceptionRes) ? exceptionRes['message'][0] : exceptionRes['message']) : exceptionRes;
const statusCode = exception.getStatus() || 400;
const status = exception instanceof common_1.HttpException ? exception.getStatus() : common_1.HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status);
response.header('Content-Type', 'application/json; charset=utf-8');
response.send(result_1.Result.fail(statusCode, Array.isArray(message) ? message[0] : message));
const exceptionRes = exception.getResponse() || 'Internal server error';
const message = (exceptionRes === null || exceptionRes === void 0 ? void 0 : exceptionRes.message)
? Array.isArray(exceptionRes)
? exceptionRes['message'][0]
: exceptionRes['message']
: exceptionRes;
const status = exception instanceof common_1.HttpException
? exception.getStatus()
: common_1.HttpStatus.INTERNAL_SERVER_ERROR;
if (status === common_1.HttpStatus.NOT_FOUND) {
response.redirect('/');
}
else {
const statusCode = status || 400;
response.status(status);
response.header('Content-Type', 'application/json; charset=utf-8');
response.send(result_1.Result.fail(statusCode, Array.isArray(message) ? message[0] : message));
}
}
};
AllExceptionsFilter = __decorate([
Expand Down
5 changes: 2 additions & 3 deletions dist/common/utils/getTokenCount.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTokenCount = void 0;
const tiktoken_1 = require("@dqbd/tiktoken");
const gpt_tokenizer_1 = require("gpt-tokenizer");
const getTokenCount = async (input) => {
let text = '';
if (Array.isArray(input)) {
Expand All @@ -25,7 +25,6 @@ const getTokenCount = async (input) => {
text = String(input);
}
text = text.replace(/<\|endoftext\|>/g, '');
const tokenizer = (0, tiktoken_1.get_encoding)('cl100k_base');
return tokenizer.encode(text).length;
return (0, gpt_tokenizer_1.encode)(text).length;
};
exports.getTokenCount = getTokenCount;
7 changes: 6 additions & 1 deletion dist/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ async function bootstrap() {
app.useLogger(app.get(custom_logger_service_1.CustomLoggerService));
app.use(compression());
app.use(xmlBodyParser());
app.enableCors();
app.enableCors({
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
});
app.setGlobalPrefix('/api');
app.useGlobalInterceptors(new transform_interceptor_1.TransformInterceptor());
app.useGlobalFilters(new typeOrmQueryFailed_filter_1.TypeOrmQueryFailedFilter());
Expand Down
34 changes: 31 additions & 3 deletions dist/modules/ai/lumaVideo.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ exports.LumaVideoService = void 0;
const common_1 = require("@nestjs/common");
const axios_1 = require("axios");
const chatLog_service_1 = require("../chatLog/chatLog.service");
const globalConfig_service_1 = require("../globalConfig/globalConfig.service");
const upload_service_1 = require("../upload/upload.service");
let LumaVideoService = class LumaVideoService {
constructor(chatLogService) {
constructor(chatLogService, globalConfigService, uploadService) {
this.chatLogService = chatLogService;
this.globalConfigService = globalConfigService;
this.uploadService = uploadService;
}
async lumaVideo(inputs) {
var _a, _b, _c;
Expand All @@ -32,12 +36,15 @@ let LumaVideoService = class LumaVideoService {
let payloadJson = {};
const headers = { Authorization: `Bearer ${apiKey}` };
url = `${proxyUrl}/luma/generations/`;
const aspectRatio = '16:9';
const aspectRatio = extraParam.size || '16:9';
payloadJson = {
user_prompt: prompt,
aspect_ratio: aspectRatio,
expand_prompt: true,
};
if (fileInfo) {
payloadJson['image_url'] = fileInfo;
}
common_1.Logger.log(`正在准备发送请求到 ${url},payload: ${JSON.stringify(payloadJson)}, headers: ${JSON.stringify(headers)}`, 'LumaService');
try {
response = await axios_1.default.post(url, payloadJson, { headers });
Expand Down Expand Up @@ -150,6 +157,25 @@ let LumaVideoService = class LumaVideoService {
result.taskId = responses.id;
result.taskData = JSON.stringify(responses);
result.fileInfo = responses.video.url;
try {
const localStorageStatus = await this.globalConfigService.getConfigs([
'localStorageStatus',
]);
if (Number(localStorageStatus)) {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const currentDate = `${year}${month}/${day}`;
result.fileInfo = await this.uploadService.uploadFileFromUrl({
url: responses.video.download_url,
dir: `video/luma/${currentDate}`,
});
}
}
catch (error) {
common_1.Logger.error(`上传文件失败: ${error.message}`, 'LumaService');
}
result.answer = `提示词: "${responses.prompt}"`;
onSuccess(result);
clearInterval(interval);
Expand Down Expand Up @@ -180,6 +206,8 @@ let LumaVideoService = class LumaVideoService {
};
LumaVideoService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [chatLog_service_1.ChatLogService])
__metadata("design:paramtypes", [chatLog_service_1.ChatLogService,
globalConfig_service_1.GlobalConfigService,
upload_service_1.UploadService])
], LumaVideoService);
exports.LumaVideoService = LumaVideoService;
54 changes: 40 additions & 14 deletions dist/modules/ai/midjourneyDraw.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ let MidjourneyService = class MidjourneyService {
this.chatLogService = chatLogService;
}
async midjourneyDraw(inputs) {
var _a, _b;
const { id, apiKey, proxyUrl, action, drawId, prompt, usePrompt, customId, timeout, assistantLogId, } = inputs;
var _a, _b, _c, _d;
const { id, apiKey, proxyUrl, action, drawId, prompt, usePrompt, customId, timeout, fileInfo, assistantLogId, } = inputs;
let result = {
text: '',
fileInfo: '',
Expand All @@ -34,22 +34,49 @@ let MidjourneyService = class MidjourneyService {
let response;
let retryCount = 0;
let url = '';
const headers = { 'mj-api-secret': apiKey };
common_1.Logger.debug(`当前任务类型: ${action}`, 'MidjourneyService');
while (retryCount < 3) {
let payloadJson = {};
try {
if (action === 'IMAGINE') {
url = `${proxyUrl}/mj/submit/imagine`;
payloadJson = { prompt: usePrompt };
}
else if (action === 'DESCRIBE') {
url = `${proxyUrl}/mj/submit/describe`;
if (fileInfo) {
const response = await fetch(fileInfo);
const blob = await response.blob();
const buffer = Buffer.from(await blob.arrayBuffer());
const base64String = buffer.toString('base64');
payloadJson = { base64: `data:image/png;base64,${base64String}` };
}
else {
return;
}
}
else if (action === 'PICREADER') {
url = `${proxyUrl}/mj/submit/action`;
payloadJson = { taskId: drawId, customId: customId };
response = await axios_1.default.post(url, payloadJson, { headers });
if ((response === null || response === void 0 ? void 0 : response.status) === 200 && ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.result)) {
url = `${proxyUrl}/mj/submit/modal`;
payloadJson = { taskId: (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.result };
}
}
else {
url = `${proxyUrl}/mj/submit/action`;
payloadJson = { taskId: drawId, customId: customId };
}
const headers = { 'mj-api-secret': apiKey };
common_1.Logger.log(`正在准备发送请求到 ${url},payload: ${JSON.stringify(payloadJson)}, headers: ${JSON.stringify(headers)}`, 'MidjourneyService');
response = await axios_1.default.post(url, payloadJson, { headers });
if ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.result) {
result.drawId = (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.result;
if ((response === null || response === void 0 ? void 0 : response.status) === 200 && ((_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.result)) {
common_1.Logger.debug(`收到响应: ${JSON.stringify(response.data)}`, 'MidjourneyService');
result.drawId = (_d = response === null || response === void 0 ? void 0 : response.data) === null || _d === void 0 ? void 0 : _d.result;
result.state = 2;
result.answer = '绘画任务提交成功';
common_1.Logger.log(`绘画任务提交成功, 绘画ID: ${response.data.result}`, 'MidjourneyService');
break;
}
else {
Expand All @@ -59,14 +86,16 @@ let MidjourneyService = class MidjourneyService {
catch (error) {
retryCount++;
if (retryCount >= 3) {
result.answer = '任务提交失败,请检查提示词后重试';
result.status = 5;
common_1.Logger.log(`绘画任务提交失败, 请检查后台配置或者稍后重试! ${error}`, 'MidjourneyService');
}
}
}
this.pollMjDrawingResult({
proxyUrl,
apiKey,
drawId: response.data.result,
drawId: result.drawId,
timeout,
prompt,
onSuccess: async (data) => {
Expand All @@ -78,27 +107,26 @@ let MidjourneyService = class MidjourneyService {
drawId: data === null || data === void 0 ? void 0 : data.drawId,
customId: data === null || data === void 0 ? void 0 : data.customId,
});
common_1.Logger.log('绘图成功!');
common_1.Logger.log('绘图成功!', 'MidjourneyService');
},
onDrawing: async (data) => {
await this.chatLogService.updateChatLog(assistantLogId, {
answer: (data === null || data === void 0 ? void 0 : data.answer) || '绘制中',
progress: data === null || data === void 0 ? void 0 : data.progress,
status: 2,
});
common_1.Logger.log(`绘制中!绘制进度${data === null || data === void 0 ? void 0 : data.progress}`);
common_1.Logger.log(`绘制中!绘制进度${data === null || data === void 0 ? void 0 : data.progress}`, 'MidjourneyService');
},
onFailure: async (data) => {
await this.chatLogService.updateChatLog(assistantLogId, {
answer: '绘图失败',
status: data.status,
});
common_1.Logger.log('绘图失败');
common_1.Logger.log('绘图失败', 'MidjourneyService');
},
}).catch((error) => {
common_1.Logger.error('查询绘图结果时发生错误:', error, 'MidjourneyService');
});
common_1.Logger.log(`绘画任务提交成功, 绘画ID: ${response.data.result}`, 'MidjourneyService');
return result;
}
async pollMjDrawingResult(inputs) {
Expand All @@ -108,7 +136,6 @@ let MidjourneyService = class MidjourneyService {
'mjProxyImgUrl',
'mjNotUseProxy',
]);
let response;
let result = {
fileInfo: '',
drawId: '',
Expand All @@ -117,11 +144,9 @@ let MidjourneyService = class MidjourneyService {
progress: 0,
answer: '',
};
let payloadJson = {};
const startTime = Date.now();
const POLL_INTERVAL = 5000;
let retryCount = 0;
let pollingCount = 0;
try {
while (Date.now() - startTime < timeout) {
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
Expand All @@ -133,6 +158,7 @@ let MidjourneyService = class MidjourneyService {
const url = `${proxyUrl}/mj/task/${drawId}/fetch`;
const res = await axios_1.default.get(url, { headers });
const responses = res.data;
common_1.Logger.debug(`查询结果: ${JSON.stringify(responses)}`, 'MidjourneyService');
if (responses.status === 'SUCCESS') {
common_1.Logger.log(`绘制成功, 获取到的URL: ${responses.imageUrl}`, 'MidjourneyService');
let processedUrl = responses.imageUrl;
Expand All @@ -150,7 +176,7 @@ let MidjourneyService = class MidjourneyService {
}
if (mjNotSaveImg !== '1') {
try {
common_1.Logger.log(`------> 开始上传图片!!!`);
common_1.Logger.log(`------> 开始上传图片!!!`, 'MidjourneyService');
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
Expand Down
Loading

0 comments on commit c831009

Please sign in to comment.