feat: custom domain (#6067)

* perf: faq

* index

* delete dataset

* delete dataset

* perf: delete dataset

* init

* fix: faq

* doc

* fix: share link auth (#6063)

* standard plan add custom domain config (#6061)

* standard plan add custom domain config

* bill detail modal

* perf: vector count api

* feat: custom domain & wecom bot SaaS integration (#6047)

* feat: custom Domain type define

* feat: custom domain

* feat: wecom custom domain

* chore: i18n

* chore: i18n; team auth

* feat: wecom multi-model message support

* chore: wecom edit modal

* chore(doc): custom domain && wecom bot

* fix: type

* fix: type

* fix: file detect

* feat: fe

* fix: img name

* fix: test

* compress img

* rename

* editor initial status

* fix: chat url

* perf: s3 upload by buffer

* img

* refresh

* fix: custom domain selector (#6069)

* empty tip

* perf: s3 init

* sort provider

* fix: extend

* perf: extract filename

---------

Co-authored-by: Roy <whoeverimf5@gmail.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2025-12-09 23:33:32 +08:00
committed by GitHub
parent d354fd4d67
commit 36d1ff3679
132 changed files with 2115 additions and 629 deletions
+2 -1
View File
@@ -126,7 +126,6 @@ const AppSchema = new Schema(
}
);
AppSchema.index({ type: 1 });
AppSchema.index({ teamId: 1, updateTime: -1 });
AppSchema.index({ teamId: 1, type: 1 });
AppSchema.index(
@@ -137,5 +136,7 @@ AppSchema.index(
}
}
);
// Admin count
AppSchema.index({ type: 1 });
export const MongoApp = getMongoModel<AppType>(AppCollectionName, AppSchema);
+3 -23
View File
@@ -96,40 +96,20 @@ export async function delDatasetRelevantData({
datasetId: { $in: datasetIds }
});
// Delete dataset_data_texts in batches by datasetId
for (const datasetId of datasetIds) {
// Delete dataset_data_texts in batches by datasetId
await MongoDatasetDataText.deleteMany({
teamId,
datasetId
}).maxTimeMS(300000); // Reduce timeout for single batch
}
// Delete dataset_datas in batches by datasetId
for (const datasetId of datasetIds) {
await MongoDatasetData.deleteMany({
teamId,
datasetId
}).maxTimeMS(300000);
}
await delCollectionRelatedSource({ collections });
// Delete vector data
await deleteDatasetDataVector({ teamId, datasetIds });
// Delete dataset_data_texts in batches by datasetId
for (const datasetId of datasetIds) {
await MongoDatasetDataText.deleteMany({
teamId,
datasetId
}).maxTimeMS(300000); // Reduce timeout for single batch
}
// Delete dataset_datas in batches by datasetId
for (const datasetId of datasetIds) {
// Delete dataset_datas in batches by datasetId
await MongoDatasetData.deleteMany({
teamId,
datasetId
}).maxTimeMS(300000);
}
// Delete source: 兼容旧版的图片
await delCollectionRelatedSource({ collections });
// Delete vector data
await deleteDatasetDataVector({ teamId, datasetIds });
+1 -1
View File
@@ -148,7 +148,7 @@ const DatasetSchema = new Schema({
try {
DatasetSchema.index({ teamId: 1 });
DatasetSchema.index({ type: 1 });
DatasetSchema.index({ type: 1 }); // Admin count
DatasetSchema.index({ deleteTime: 1 }); // 添加软删除字段索引
} catch (error) {
console.log(error);
@@ -179,13 +179,13 @@ export const getFileContentFromLinks = async ({
sourceId: url,
customPdfParse
});
if (rawTextBuffer) {
return formatResponseObject({
filename: rawTextBuffer.filename || url,
url,
content: rawTextBuffer.text
});
}
// if (rawTextBuffer) {
// return formatResponseObject({
// filename: rawTextBuffer.filename || url,
// url,
// content: rawTextBuffer.text
// });
// }
try {
if (isInternalAddress(url)) {
@@ -207,23 +207,39 @@ export const getFileContentFromLinks = async ({
// Get file name
const { filename, extension, imageParsePrefix } = (() => {
const contentDisposition = response.headers['content-disposition'];
if (contentDisposition) {
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(contentDisposition);
if (matches != null && matches[1]) {
const filename = decodeURIComponent(matches[1].replace(/['"]/g, ''));
return {
filename,
extension: path.extname(filename).replace('.', ''),
imageParsePrefix: `` // TODO: 需要根据是否是聊天对话里面的外部链接来决定
};
}
}
if (isChatExternalUrl) {
const filename = urlObj.pathname.split('/').pop() || 'file';
const contentDisposition = response.headers['content-disposition'] || '';
// Priority: filename* (RFC 5987, UTF-8 encoded) > filename (traditional)
const extractFilename = (contentDisposition: string): string => {
// Try RFC 5987 filename* first (e.g., filename*=UTF-8''encoded-name)
const filenameStarRegex = /filename\*=([^']*)'([^']*)'([^;\n]*)/i;
const starMatches = filenameStarRegex.exec(contentDisposition);
if (starMatches && starMatches[3]) {
const charset = starMatches[1].toLowerCase();
const encodedFilename = starMatches[3];
// Decode percent-encoded UTF-8 filename
try {
return decodeURIComponent(encodedFilename);
} catch (error) {
addLog.warn('Failed to decode filename*', { encodedFilename, error });
}
}
// Fallback to traditional filename parameter
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i;
const matches = filenameRegex.exec(contentDisposition);
if (matches && matches[1]) {
return matches[1].replace(/['"]/g, '');
}
return '';
};
const matchFilename = extractFilename(contentDisposition);
const filename = matchFilename || urlObj.pathname.split('/').pop() || 'file';
const extension = path.extname(filename).replace('.', '');
return {
filename,
extension,