qwen-api-proxy 1.0.10
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/.env.example +49 -0
- package/LICENSE +21 -0
- package/README.md +2054 -0
- package/bin/qwen-api-proxy.js +414 -0
- package/index.js +444 -0
- package/package.json +85 -0
- package/src/Authorization.txt +17 -0
- package/src/AvailableModels.txt +26 -0
- package/src/api/chat.js +1392 -0
- package/src/api/chatHistory.js +344 -0
- package/src/api/fileUpload.js +182 -0
- package/src/api/imageGeneration.js +459 -0
- package/src/api/modelMapping.js +274 -0
- package/src/api/routes.js +2160 -0
- package/src/api/tokenManager.js +382 -0
- package/src/browser/auth.js +171 -0
- package/src/browser/browser.js +233 -0
- package/src/browser/session.js +134 -0
- package/src/config.js +116 -0
- package/src/logger/index.js +89 -0
- package/src/utils/accountSetup.js +153 -0
- package/src/utils/botSettings.js +231 -0
- package/src/utils/permissionChecker.js +205 -0
- package/src/utils/prompt.js +11 -0
- package/src/utils/proxy.js +255 -0
- package/src/utils/telegramBot.js +2977 -0
- package/src/utils/telegramNotifier.js +94 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import { logInfo, logError, logDebug } from '../logger/index.js';
|
|
6
|
+
import { SESSION_DIR, MAX_HISTORY_LENGTH } from '../config.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
const HISTORY_DIR = path.resolve(__dirname, '..', '..', SESSION_DIR, 'history');
|
|
11
|
+
|
|
12
|
+
export function initHistoryDirectory() {
|
|
13
|
+
if (!fs.existsSync(HISTORY_DIR)) {
|
|
14
|
+
fs.mkdirSync(HISTORY_DIR, { recursive: true });
|
|
15
|
+
logInfo(`Создана директория для истории чатов: ${HISTORY_DIR}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function generateChatId() {
|
|
20
|
+
return crypto.randomUUID();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createChat(chatName) {
|
|
24
|
+
const chatId = generateChatId();
|
|
25
|
+
const chatInfo = {
|
|
26
|
+
id: chatId,
|
|
27
|
+
name: chatName || `Новый чат ${new Date().toLocaleString()}`,
|
|
28
|
+
created: Date.now(),
|
|
29
|
+
messages: []
|
|
30
|
+
};
|
|
31
|
+
saveHistory(chatId, chatInfo);
|
|
32
|
+
logInfo(`Создан новый чат [${chatId}] с именем "${chatInfo.name}"`);
|
|
33
|
+
return chatId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getHistoryFilePath(chatId) {
|
|
37
|
+
return path.join(HISTORY_DIR, `${chatId}.json`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function saveHistory(chatId, data) {
|
|
41
|
+
try {
|
|
42
|
+
initHistoryDirectory();
|
|
43
|
+
const historyFilePath = getHistoryFilePath(chatId);
|
|
44
|
+
fs.writeFileSync(historyFilePath, JSON.stringify(data, null, 2), 'utf8');
|
|
45
|
+
logDebug(`История чата ${chatId} успешно сохранена`);
|
|
46
|
+
return true;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logError(`Ошибка при сохранении истории чата ${chatId}`, error);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function loadHistory(chatId) {
|
|
54
|
+
try {
|
|
55
|
+
const historyFilePath = getHistoryFilePath(chatId);
|
|
56
|
+
if (fs.existsSync(historyFilePath)) {
|
|
57
|
+
const rawData = fs.readFileSync(historyFilePath, 'utf8');
|
|
58
|
+
logDebug(`Данные чата ${chatId} успешно загружены`);
|
|
59
|
+
|
|
60
|
+
let data;
|
|
61
|
+
try {
|
|
62
|
+
data = JSON.parse(rawData);
|
|
63
|
+
logDebug(`Данные чата ${chatId} успешно распарсены`);
|
|
64
|
+
} catch (parseErr) {
|
|
65
|
+
logError(`Ошибка при парсинге данных чата ${chatId}`, parseErr);
|
|
66
|
+
return {
|
|
67
|
+
id: chatId,
|
|
68
|
+
name: `Восстановленный чат ${new Date().toLocaleString()}`,
|
|
69
|
+
created: Date.now(),
|
|
70
|
+
messages: []
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Поддержка обратной совместимости со старым форматом
|
|
75
|
+
if (Array.isArray(data)) {
|
|
76
|
+
logDebug(`Чат ${chatId} использует устаревший формат, выполняется конвертация`);
|
|
77
|
+
return {
|
|
78
|
+
id: chatId,
|
|
79
|
+
name: `Чат от ${new Date().toLocaleString()}`,
|
|
80
|
+
created: Date.now(),
|
|
81
|
+
messages: data,
|
|
82
|
+
wasConverted: true
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Проверяем наличие обязательных полей
|
|
87
|
+
if (!data.messages) {
|
|
88
|
+
logInfo(`Чат ${chatId} не содержит сообщений, инициализируем пустой массив`);
|
|
89
|
+
data.messages = [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!data.name) {
|
|
93
|
+
data.name = `Чат ${chatId.substring(0, 6)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!data.created) {
|
|
97
|
+
data.created = Date.now();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!data.id) {
|
|
101
|
+
data.id = chatId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return data;
|
|
105
|
+
} else {
|
|
106
|
+
logInfo(`Файл истории для чата ${chatId} не найден`);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
logError(`Ошибка при загрузке истории чата ${chatId}`, error);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Если не удалось загрузить, создаем новые данные
|
|
113
|
+
logInfo(`Создаем новую историю для чата ${chatId}`);
|
|
114
|
+
return {
|
|
115
|
+
id: chatId,
|
|
116
|
+
name: `Новый чат ${new Date().toLocaleString()}`,
|
|
117
|
+
created: Date.now(),
|
|
118
|
+
messages: []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function chatExists(chatId) {
|
|
123
|
+
const historyFilePath = getHistoryFilePath(chatId);
|
|
124
|
+
const exists = fs.existsSync(historyFilePath);
|
|
125
|
+
logDebug(`Проверка существования чата ${chatId}: ${exists ? 'найден' : 'не найден'}`);
|
|
126
|
+
return exists;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function renameChat(chatId, newName) {
|
|
130
|
+
try {
|
|
131
|
+
if (!chatExists(chatId)) {
|
|
132
|
+
logError(`Попытка переименовать несуществующий чат ${chatId}`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const chatData = loadHistory(chatId);
|
|
137
|
+
const oldName = chatData.name;
|
|
138
|
+
chatData.name = newName;
|
|
139
|
+
const success = saveHistory(chatId, chatData);
|
|
140
|
+
if (success) {
|
|
141
|
+
logInfo(`Чат ${chatId} переименован: "${oldName}" -> "${newName}"`);
|
|
142
|
+
} else {
|
|
143
|
+
logError(`Не удалось переименовать чат ${chatId}`);
|
|
144
|
+
}
|
|
145
|
+
return success;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logError(`Ошибка при переименовании чата ${chatId}`, error);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function addUserMessage(chatId, content) {
|
|
153
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
154
|
+
const messageId = crypto.randomUUID();
|
|
155
|
+
|
|
156
|
+
// Определяем тип содержимого и его длину для логирования
|
|
157
|
+
let contentDesc;
|
|
158
|
+
if (Array.isArray(content)) {
|
|
159
|
+
// Составное сообщение (текст + изображения)
|
|
160
|
+
const textParts = content.filter(item => item.type === 'text');
|
|
161
|
+
const imageParts = content.filter(item => item.type === 'image');
|
|
162
|
+
const fileParts = content.filter(item => item.type === 'file');
|
|
163
|
+
|
|
164
|
+
contentDesc = `составное сообщение (${textParts.length} текст., ${imageParts.length} изобр., ${fileParts.length} файл.)`;
|
|
165
|
+
} else if (typeof content === 'object' && content !== null) {
|
|
166
|
+
contentDesc = 'объект-сообщение';
|
|
167
|
+
} else {
|
|
168
|
+
contentDesc = `текст длиной ${String(content).length}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const message = {
|
|
172
|
+
id: messageId,
|
|
173
|
+
role: "user",
|
|
174
|
+
content: content,
|
|
175
|
+
timestamp: timestamp,
|
|
176
|
+
chat_type: "t2t"
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
logInfo(`Добавление сообщения пользователя в чат ${chatId}: ${contentDesc}`);
|
|
180
|
+
return addMessageToHistory(chatId, message);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function addAssistantMessage(chatId, content, info = {}) {
|
|
184
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
185
|
+
const messageId = crypto.randomUUID();
|
|
186
|
+
|
|
187
|
+
const message = {
|
|
188
|
+
id: messageId,
|
|
189
|
+
role: "assistant",
|
|
190
|
+
content: content,
|
|
191
|
+
timestamp: timestamp,
|
|
192
|
+
info: info,
|
|
193
|
+
chat_type: "t2t"
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
logInfo(`Добавление ответа ассистента в чат ${chatId}, длина: ${content.length}`);
|
|
197
|
+
return addMessageToHistory(chatId, message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function addMessageToHistory(chatId, message) {
|
|
201
|
+
try {
|
|
202
|
+
let chatData = loadHistory(chatId);
|
|
203
|
+
|
|
204
|
+
if (chatData.messages.length >= MAX_HISTORY_LENGTH) {
|
|
205
|
+
logInfo(`Чат ${chatId} достиг максимальной длины (${MAX_HISTORY_LENGTH}), удаляем старые сообщения`);
|
|
206
|
+
chatData.messages = [chatData.messages[0], ...chatData.messages.slice(chatData.messages.length - MAX_HISTORY_LENGTH + 2)];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
chatData.messages.push(message);
|
|
210
|
+
saveHistory(chatId, chatData);
|
|
211
|
+
logDebug(`Сообщение ${message.id} успешно добавлено в чат ${chatId}`);
|
|
212
|
+
|
|
213
|
+
return message.id;
|
|
214
|
+
} catch (error) {
|
|
215
|
+
logError(`Ошибка при добавлении сообщения в историю чата ${chatId}`, error);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function getAllChats() {
|
|
221
|
+
try {
|
|
222
|
+
initHistoryDirectory();
|
|
223
|
+
const files = fs.readdirSync(HISTORY_DIR);
|
|
224
|
+
logDebug(`Получен список файлов чатов: ${files.length} файлов`);
|
|
225
|
+
|
|
226
|
+
let convertedCount = 0;
|
|
227
|
+
const chats = files
|
|
228
|
+
.filter(file => file.endsWith('.json'))
|
|
229
|
+
.map(file => {
|
|
230
|
+
const chatId = file.replace('.json', '');
|
|
231
|
+
const chatData = loadHistory(chatId);
|
|
232
|
+
|
|
233
|
+
if (chatData.wasConverted) {
|
|
234
|
+
convertedCount++;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
id: chatId,
|
|
239
|
+
name: chatData.name || `Чат ${chatId.substring(0, 6)}`,
|
|
240
|
+
created: chatData.created || 0,
|
|
241
|
+
messageCount: chatData.messages ? chatData.messages.length : 0,
|
|
242
|
+
userMessageCount: chatData.messages ?
|
|
243
|
+
chatData.messages.filter(m => m.role === 'user').length : 0
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (convertedCount > 0) {
|
|
248
|
+
logInfo(`Конвертировано ${convertedCount} чатов из устаревшего формата`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
logInfo(`Обработано ${chats.length} чатов`);
|
|
252
|
+
return chats.sort((a, b) => b.created - a.created);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
logError('Ошибка при получении списка чатов', error);
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function deleteChat(chatId) {
|
|
260
|
+
try {
|
|
261
|
+
const historyFilePath = getHistoryFilePath(chatId);
|
|
262
|
+
if (fs.existsSync(historyFilePath)) {
|
|
263
|
+
fs.unlinkSync(historyFilePath);
|
|
264
|
+
logInfo(`Чат ${chatId} успешно удален`);
|
|
265
|
+
return true;
|
|
266
|
+
} else {
|
|
267
|
+
logError(`Попытка удаления несуществующего чата ${chatId}`);
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
logError(`Ошибка при удалении чата ${chatId}`, error);
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function deleteChatsAutomatically(criteria = {}) {
|
|
276
|
+
try {
|
|
277
|
+
const { olderThan, userMessageCountLessThan, messageCountLessThan, maxChats } = criteria;
|
|
278
|
+
logInfo(`Автоудаление чатов с критериями: ${JSON.stringify(criteria)}`);
|
|
279
|
+
|
|
280
|
+
const chats = getAllChats();
|
|
281
|
+
logInfo(`Найдено ${chats.length} чатов для проверки`);
|
|
282
|
+
|
|
283
|
+
let chatsToDelete = [...chats];
|
|
284
|
+
|
|
285
|
+
// Фильтрация по возрасту (в миллисекундах)
|
|
286
|
+
if (olderThan) {
|
|
287
|
+
const cutoffTime = Date.now() - olderThan;
|
|
288
|
+
const oldChatsCount = chatsToDelete.filter(chat => chat.created < cutoffTime).length;
|
|
289
|
+
logInfo(`Чатов старше ${olderThan}мс (${new Date(cutoffTime).toLocaleString()}): ${oldChatsCount}`);
|
|
290
|
+
chatsToDelete = chatsToDelete.filter(chat => chat.created < cutoffTime);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (userMessageCountLessThan !== undefined) {
|
|
294
|
+
const lowUserMsgChatsCount = chatsToDelete.filter(chat =>
|
|
295
|
+
chat.userMessageCount < userMessageCountLessThan).length;
|
|
296
|
+
logInfo(`Чатов с менее чем ${userMessageCountLessThan} сообщений пользователя: ${lowUserMsgChatsCount}`);
|
|
297
|
+
chatsToDelete = chatsToDelete.filter(chat =>
|
|
298
|
+
chat.userMessageCount < userMessageCountLessThan);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (messageCountLessThan !== undefined) {
|
|
302
|
+
const lowMsgChatsCount = chatsToDelete.filter(chat =>
|
|
303
|
+
chat.messageCount < messageCountLessThan).length;
|
|
304
|
+
logInfo(`Чатов с менее чем ${messageCountLessThan} сообщений всего: ${lowMsgChatsCount}`);
|
|
305
|
+
chatsToDelete = chatsToDelete.filter(chat =>
|
|
306
|
+
chat.messageCount < messageCountLessThan);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (maxChats && chats.length > maxChats) {
|
|
310
|
+
logInfo(`Общее количество чатов (${chats.length}) превышает лимит (${maxChats}), удаляем старые чаты`);
|
|
311
|
+
const sortedChats = [...chats].sort((a, b) => a.created - b.created);
|
|
312
|
+
const oldestChats = sortedChats.slice(0, chats.length - maxChats);
|
|
313
|
+
|
|
314
|
+
oldestChats.forEach(chat => {
|
|
315
|
+
if (!chatsToDelete.some(c => c.id === chat.id)) {
|
|
316
|
+
chatsToDelete.push(chat);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Удаление выбранных чатов
|
|
322
|
+
const deletedChats = [];
|
|
323
|
+
logInfo(`Найдено ${chatsToDelete.length} чатов для удаления`);
|
|
324
|
+
|
|
325
|
+
for (const chat of chatsToDelete) {
|
|
326
|
+
if (deleteChat(chat.id)) {
|
|
327
|
+
deletedChats.push(chat.id);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
logInfo(`Удалено ${deletedChats.length} чатов`);
|
|
332
|
+
return {
|
|
333
|
+
success: true,
|
|
334
|
+
deletedCount: deletedChats.length,
|
|
335
|
+
deletedChats
|
|
336
|
+
};
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logError('Ошибка при автоматическом удалении чатов', error);
|
|
339
|
+
return {
|
|
340
|
+
success: false,
|
|
341
|
+
error: error.message
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { getBrowserContext } from '../browser/browser.js';
|
|
2
|
+
import { logInfo, logError } from '../logger/index.js';
|
|
3
|
+
import { getAuthToken, extractAuthToken, pagePool } from './chat.js';
|
|
4
|
+
import { getAvailableToken } from './tokenManager.js';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { STS_TOKEN_API_URL, OSS_SDK_URL, UPLOADS_DIR } from '../config.js';
|
|
8
|
+
|
|
9
|
+
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
|
|
10
|
+
const DOCUMENT_EXTENSIONS = ['.pdf', '.doc', '.docx', '.txt'];
|
|
11
|
+
const DEFAULT_FILE_TYPE = 'file';
|
|
12
|
+
const IMAGE_FILE_TYPE = 'image';
|
|
13
|
+
const DOCUMENT_FILE_TYPE = 'document';
|
|
14
|
+
|
|
15
|
+
function validateBrowserContext() {
|
|
16
|
+
const browserContext = getBrowserContext();
|
|
17
|
+
if (!browserContext) throw new Error('Браузер не инициализирован');
|
|
18
|
+
return browserContext;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function validateAuthToken(browserContext) {
|
|
22
|
+
const tokenObj = await getAvailableToken();
|
|
23
|
+
if (tokenObj?.token) {
|
|
24
|
+
logInfo(`Используется токен из tokenManager: ${tokenObj.id}`);
|
|
25
|
+
return tokenObj.token;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let token = getAuthToken();
|
|
29
|
+
if (!token) {
|
|
30
|
+
logInfo('Токен авторизации не найден в памяти, пытаемся извлечь из браузера');
|
|
31
|
+
token = await extractAuthToken(browserContext);
|
|
32
|
+
if (!token) throw new Error('Не удалось получить токен авторизации');
|
|
33
|
+
}
|
|
34
|
+
return token;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getStsToken(fileInfo) {
|
|
38
|
+
const browserContext = validateBrowserContext();
|
|
39
|
+
const token = await validateAuthToken(browserContext);
|
|
40
|
+
logInfo(`Запрос STS токена для файла: ${fileInfo.filename}`);
|
|
41
|
+
|
|
42
|
+
let page = null;
|
|
43
|
+
try {
|
|
44
|
+
page = await pagePool.getPage(browserContext);
|
|
45
|
+
const result = await page.evaluate(async (data) => {
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(data.apiUrl, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${data.token}`, 'Accept': 'application/json' },
|
|
50
|
+
body: JSON.stringify(data.fileInfo)
|
|
51
|
+
});
|
|
52
|
+
if (response.ok) return { success: true, data: await response.json() };
|
|
53
|
+
return { success: false, status: response.status, statusText: response.statusText, errorBody: await response.text() };
|
|
54
|
+
} catch (error) { return { success: false, error: error.toString() }; }
|
|
55
|
+
}, { apiUrl: STS_TOKEN_API_URL, token, fileInfo });
|
|
56
|
+
|
|
57
|
+
if (result.success) {
|
|
58
|
+
logInfo(`STS токен успешно получен для файла: ${fileInfo.filename}`);
|
|
59
|
+
return result.data;
|
|
60
|
+
}
|
|
61
|
+
const errorMsg = result.errorBody || result.error || result.statusText || 'Unknown error';
|
|
62
|
+
logError(`Ошибка при получении STS токена: status=${result.status}, error=${errorMsg}`);
|
|
63
|
+
throw new Error(`Ошибка получения STS токена: ${result.statusText || ''} ${errorMsg}`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logError(`Ошибка при получении STS токена: ${error.message}`, error);
|
|
66
|
+
throw error;
|
|
67
|
+
} finally {
|
|
68
|
+
if (page) { try { pagePool.releasePage(page); } catch (e) { logError('Ошибка при возврате страницы в пул', e); } }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function uploadFile(filePath, stsData) {
|
|
73
|
+
const browserContext = validateBrowserContext();
|
|
74
|
+
logInfo(`Начало загрузки файла: ${filePath}`);
|
|
75
|
+
|
|
76
|
+
if (!stsData?.file_path || !stsData?.access_key_id || !stsData?.access_key_secret ||
|
|
77
|
+
!stsData?.security_token || !stsData?.region || !stsData?.bucketname) {
|
|
78
|
+
throw new Error('Некорректные или неполные данные STS токена');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
logInfo(`[OSS] Загрузка через браузер`);
|
|
82
|
+
logInfo(`[OSS] Регион: ${stsData.region}, Бакет: ${stsData.bucketname}`);
|
|
83
|
+
if (stsData.endpoint) logInfo(`[OSS] Endpoint: ${stsData.endpoint}`);
|
|
84
|
+
logInfo(`[OSS] File path: ${stsData.file_path}`);
|
|
85
|
+
logInfo(`[OSS] File URL: ${stsData.file_url}`);
|
|
86
|
+
|
|
87
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
88
|
+
const fileBase64 = fileBuffer.toString('base64');
|
|
89
|
+
logInfo(`[OSS] Размер файла: ${fileBuffer.length} байт`);
|
|
90
|
+
|
|
91
|
+
let page = null;
|
|
92
|
+
try {
|
|
93
|
+
page = await pagePool.getPage(browserContext);
|
|
94
|
+
const result = await page.evaluate(async (data) => {
|
|
95
|
+
try {
|
|
96
|
+
if (typeof window.OSS === 'undefined') {
|
|
97
|
+
console.log('[OSS] Загрузка OSS SDK...');
|
|
98
|
+
await new Promise((resolve, reject) => {
|
|
99
|
+
const script = document.createElement('script');
|
|
100
|
+
script.src = data.ossSdkUrl;
|
|
101
|
+
script.onload = resolve;
|
|
102
|
+
script.onerror = () => reject(new Error('Failed to load OSS SDK'));
|
|
103
|
+
document.head.appendChild(script);
|
|
104
|
+
});
|
|
105
|
+
console.log('[OSS] SDK загружен успешно');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('[OSS] Создание Blob из файла...');
|
|
109
|
+
const blob = new Blob([Uint8Array.from(atob(data.fileBase64), c => c.charCodeAt(0))]);
|
|
110
|
+
console.log(`[OSS] Blob создан, размер: ${blob.size} байт`);
|
|
111
|
+
|
|
112
|
+
console.log('[OSS] Создание OSS клиента...');
|
|
113
|
+
const client = new window.OSS({
|
|
114
|
+
region: data.stsData.region,
|
|
115
|
+
accessKeyId: data.stsData.access_key_id,
|
|
116
|
+
accessKeySecret: data.stsData.access_key_secret,
|
|
117
|
+
stsToken: data.stsData.security_token,
|
|
118
|
+
bucket: data.stsData.bucketname,
|
|
119
|
+
secure: true
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
console.log(`[OSS] Загрузка файла в OSS: ${data.stsData.file_path}`);
|
|
123
|
+
await client.put(data.stsData.file_path, blob);
|
|
124
|
+
console.log('[OSS] Файл успешно загружен');
|
|
125
|
+
|
|
126
|
+
return { success: true };
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(`[OSS Browser] Ошибка: ${error.toString()}`);
|
|
129
|
+
console.error(`[OSS Browser] Stack: ${error.stack || 'N/A'}`);
|
|
130
|
+
return { success: false, error: error.toString() };
|
|
131
|
+
}
|
|
132
|
+
}, {
|
|
133
|
+
fileBase64,
|
|
134
|
+
ossSdkUrl: OSS_SDK_URL,
|
|
135
|
+
stsData: { region: stsData.region, bucketname: stsData.bucketname, file_path: stsData.file_path, access_key_id: stsData.access_key_id, access_key_secret: stsData.access_key_secret, security_token: stsData.security_token }
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (result.success) {
|
|
139
|
+
logInfo(`[OSS] Загрузка завершена успешно`);
|
|
140
|
+
return { success: true, fileName: path.basename(filePath), url: stsData.file_url, fileId: stsData.file_id, filePath: stsData.file_path };
|
|
141
|
+
}
|
|
142
|
+
logError(`[OSS] Ошибка загрузки: ${result.error}`);
|
|
143
|
+
throw new Error(`Ошибка загрузки в OSS: ${result.error}`);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logError(`Ошибка при загрузке файла в OSS: ${error.message}`, error);
|
|
146
|
+
throw error;
|
|
147
|
+
} finally {
|
|
148
|
+
if (page) { try { pagePool.releasePage(page); } catch (e) { logError('Ошибка при возврате страницы в пул', e); } }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function uploadFileToQwen(filePath) {
|
|
153
|
+
try {
|
|
154
|
+
if (!fs.existsSync(filePath)) throw new Error(`Файл не найден: ${filePath}`);
|
|
155
|
+
|
|
156
|
+
const fileName = path.basename(filePath);
|
|
157
|
+
const fileSize = fs.statSync(filePath).size;
|
|
158
|
+
const fileExt = path.extname(fileName).toLowerCase();
|
|
159
|
+
|
|
160
|
+
let fileType = DEFAULT_FILE_TYPE;
|
|
161
|
+
if (IMAGE_EXTENSIONS.includes(fileExt)) fileType = IMAGE_FILE_TYPE;
|
|
162
|
+
else if (DOCUMENT_EXTENSIONS.includes(fileExt)) fileType = DOCUMENT_FILE_TYPE;
|
|
163
|
+
|
|
164
|
+
logInfo(`📤 Загрузка файла в Qwen: ${fileName} (${fileSize} байт, тип: ${fileType})`);
|
|
165
|
+
|
|
166
|
+
const fileInfo = { filename: fileName, filesize: fileSize, filetype: fileType };
|
|
167
|
+
const stsData = await getStsToken(fileInfo);
|
|
168
|
+
|
|
169
|
+
logInfo(`✅ STS токен получен, начинаем загрузку в OSS`);
|
|
170
|
+
|
|
171
|
+
const uploadResult = await uploadFile(filePath, stsData);
|
|
172
|
+
|
|
173
|
+
logInfo(`✅ Файл успешно загружен в Qwen`);
|
|
174
|
+
|
|
175
|
+
return { ...uploadResult, fileInfo, stsData };
|
|
176
|
+
} catch (error) {
|
|
177
|
+
logError(`❌ Ошибка в процессе загрузки файла: ${error.message}`, error);
|
|
178
|
+
return { success: false, error: error.message };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default { getStsToken, uploadFile, uploadFileToQwen };
|