save_load_learned
This commit is contained in:
@@ -48,4 +48,5 @@ class Config:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ASE_ENGINE_URL = GLOBAL_CONFIG['ASE_ENGINE']['url']
|
ASE_ENGINE_URL = GLOBAL_CONFIG['ASE_ENGINE']['url']
|
||||||
|
ASE_ENGINE_URL_TOKEN = GLOBAL_CONFIG['ASE_ENGINE']['url_token']
|
||||||
ASE_ENGINE_NAMESPACE = GLOBAL_CONFIG['ASE_ENGINE']['namespace']
|
ASE_ENGINE_NAMESPACE = GLOBAL_CONFIG['ASE_ENGINE']['namespace']
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
# myapp/services/course_service.py
|
# myapp/services/course_service.py
|
||||||
import re
|
import re
|
||||||
from typing import Dict, List
|
import requests
|
||||||
|
import json
|
||||||
|
from typing import Dict, List, Optional
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -450,10 +452,10 @@ def _default_markdowns(lesson_name: str):
|
|||||||
|
|
||||||
## 阶段一:示例阶段
|
## 阶段一:示例阶段
|
||||||
### 步骤A
|
### 步骤A
|
||||||
请基于“步骤A”的教学目标与材料,生成指导性提示词(尽量结构化,给出评估标准和示例)。
|
请基于"步骤A"的教学目标与材料,生成指导性提示词(尽量结构化,给出评估标准和示例)。
|
||||||
|
|
||||||
### 步骤B
|
### 步骤B
|
||||||
请基于“步骤B”的教学目标与材料,生成指导性提示词。
|
请基于"步骤B"的教学目标与材料,生成指导性提示词。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
score_prompt_md = f"""# {lesson_name}
|
score_prompt_md = f"""# {lesson_name}
|
||||||
@@ -463,8 +465,134 @@ def _default_markdowns(lesson_name: str):
|
|||||||
|
|
||||||
## 阶段一:示例阶段
|
## 阶段一:示例阶段
|
||||||
### 步骤A
|
### 步骤A
|
||||||
对学员在“步骤A”的输出进行评分与文字反馈,分数区间 0-100,并输出 JSON:```{{"score": <int>, "reasons": "<string>", "advices": "<string>"}}```
|
对学员在"步骤A"的输出进行评分与文字反馈,分数区间 0-100,并输出 JSON:```{{"score": <int>, "reasons": "<string>", "advices": "<string>"}}```
|
||||||
### 步骤B
|
### 步骤B
|
||||||
同上,对“步骤B”输出进行评分与反馈。
|
同上,对"步骤B"输出进行评分与反馈。
|
||||||
"""
|
"""
|
||||||
return lesson_md, prompt_md, score_prompt_md
|
return lesson_md, prompt_md, score_prompt_md
|
||||||
|
|
||||||
|
def save_learning_progress(user_uuid: str, chatmanager, user_id: str) -> bool:
|
||||||
|
"""
|
||||||
|
保存用户学习进度到MongoDB
|
||||||
|
|
||||||
|
参数:
|
||||||
|
- user_uuid: 用户UUID
|
||||||
|
- chatmanager: ChatManager实例,包含学习进度数据
|
||||||
|
- user_id: 用户ID
|
||||||
|
|
||||||
|
返回:
|
||||||
|
- bool: 保存是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取MongoDB连接
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
|
||||||
|
# 获取MultiAgents框架中的对话列表
|
||||||
|
dialog_list = get_multiagents_dialog_list(user_id)
|
||||||
|
|
||||||
|
# 构建学习进度数据
|
||||||
|
progress_data = {
|
||||||
|
"user_uuid": user_uuid,
|
||||||
|
"user_id": user_id,
|
||||||
|
"material_id": chatmanager.material_id,
|
||||||
|
"chapter_name": chatmanager.chapter_name,
|
||||||
|
"lesson_name": chatmanager.lesson_name,
|
||||||
|
"chapter_chain_now": chatmanager.chapter_chain_now,
|
||||||
|
"chat_historys": chatmanager.chat_historys,
|
||||||
|
"scores": chatmanager.scores,
|
||||||
|
"multiagents_dialogs": dialog_list,
|
||||||
|
"updated_at": datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保存到MongoDB,使用upsert操作(如果存在则更新,不存在则插入)
|
||||||
|
mongo.db.learning_progress.update_one(
|
||||||
|
{
|
||||||
|
"user_uuid": user_uuid,
|
||||||
|
"material_id": chatmanager.material_id,
|
||||||
|
"chapter_name": chatmanager.chapter_name,
|
||||||
|
"lesson_name": chatmanager.lesson_name
|
||||||
|
},
|
||||||
|
{"$set": progress_data},
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
|
||||||
|
current_app.logger.info(f"学习进度保存成功: user_uuid={user_uuid}, lesson={chatmanager.lesson_name}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"保存学习进度失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_multiagents_dialog_list(user_id: str) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
从MultiAgents框架获取用户对话列表
|
||||||
|
|
||||||
|
参数:
|
||||||
|
- user_id: 用户ID
|
||||||
|
|
||||||
|
返回:
|
||||||
|
- List[Dict]: 对话列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 从配置中获取ASE引擎URL和token
|
||||||
|
config = current_app.config
|
||||||
|
url_token = config.get("ASE_ENGINE_URL_TOKEN", "")
|
||||||
|
|
||||||
|
# 构建请求URL
|
||||||
|
base_url = config.get("ASE_ENGINE_URL", "")
|
||||||
|
api_url = f"{base_url}/api/sync/download"
|
||||||
|
|
||||||
|
# 构建请求参数
|
||||||
|
payload = {
|
||||||
|
"namespace_url": url_token, # 使用配置中的token作为namespace_url
|
||||||
|
"user_id": user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# 发送POST请求
|
||||||
|
response = requests.post(
|
||||||
|
api_url,
|
||||||
|
json=payload,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查响应
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
current_app.logger.error(f"获取MultiAgents对话列表失败: HTTP {response.status_code}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"获取MultiAgents对话列表异常: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def load_learning_progress(user_uuid: str, material_id: str, chapter_name: str, lesson_name: str) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
加载用户学习进度
|
||||||
|
|
||||||
|
参数:
|
||||||
|
- user_uuid: 用户UUID
|
||||||
|
- material_id: 课程ID
|
||||||
|
- chapter_name: 章节名
|
||||||
|
- lesson_name: 课时名
|
||||||
|
|
||||||
|
返回:
|
||||||
|
- Optional[Dict]: 学习进度数据,如果不存在则返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mongo = current_app.extensions["mongo"]
|
||||||
|
progress = mongo.db.learning_progress.find_one({
|
||||||
|
"user_uuid": user_uuid,
|
||||||
|
"material_id": material_id,
|
||||||
|
"chapter_name": chapter_name,
|
||||||
|
"lesson_name": lesson_name
|
||||||
|
})
|
||||||
|
|
||||||
|
if progress:
|
||||||
|
# 转换ObjectId为字符串
|
||||||
|
if "_id" in progress:
|
||||||
|
progress["_id"] = str(progress["_id"])
|
||||||
|
return progress
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"加载学习进度失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from flask_socketio import Namespace, join_room, leave_room, emit
|
|||||||
from ..function_tools import judge
|
from ..function_tools import judge
|
||||||
from ..function_tools import next_chapter
|
from ..function_tools import next_chapter
|
||||||
from ..services.backboard_service import realtime_response
|
from ..services.backboard_service import realtime_response
|
||||||
from ..services.markdown_service import load_full_markdown_file
|
from ..services.course_service import load_full_markdown_file, save_learning_progress
|
||||||
from ..extension_ase.ase_client import HSAEngineClient
|
from ..extension_ase.ase_client import HSAEngineClient
|
||||||
from ..services.user_service import get_or_load_current_user
|
from ..services.user_service import get_or_load_current_user
|
||||||
|
|
||||||
@@ -71,6 +71,30 @@ class AgentNamespace(Namespace):
|
|||||||
chatmanager = user_uuid2chatmanager[user_uuid]
|
chatmanager = user_uuid2chatmanager[user_uuid]
|
||||||
chatmanager.function_manager.add_function(judge, {'course_id': course_id, 'root_path': f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}'})
|
chatmanager.function_manager.add_function(judge, {'course_id': course_id, 'root_path': f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}'})
|
||||||
chatmanager.function_manager.add_function(next_chapter, {'user_uuid': user_uuid})
|
chatmanager.function_manager.add_function(next_chapter, {'user_uuid': user_uuid})
|
||||||
|
|
||||||
|
# 尝试加载用户学习进度
|
||||||
|
from ..services.course_service import load_learning_progress
|
||||||
|
progress = load_learning_progress(user_uuid, course_id, chapter_name, lesson_name)
|
||||||
|
need_skip_chapters = 0
|
||||||
|
|
||||||
|
if progress:
|
||||||
|
# 恢复学习进度数据
|
||||||
|
if 'scores' in progress:
|
||||||
|
# 根据scores的数量确定需要跳过的章节数
|
||||||
|
need_skip_chapters = len(progress['scores'])
|
||||||
|
chatmanager.scores = progress['scores']
|
||||||
|
print(f"恢复用户学习进度: {user_uuid}, 已完成 {need_skip_chapters} 个章节")
|
||||||
|
|
||||||
|
# 恢复聊天历史
|
||||||
|
if 'chat_historys' in progress:
|
||||||
|
chatmanager.chat_historys = progress['chat_historys']
|
||||||
|
else:
|
||||||
|
chatmanager.chat_historys = []
|
||||||
|
|
||||||
|
# 恢复当前章节链
|
||||||
|
if 'chapter_chain_now' in progress:
|
||||||
|
chatmanager.chapter_chain_now = progress['chapter_chain_now']
|
||||||
|
else:
|
||||||
chatmanager.chat_historys = []
|
chatmanager.chat_historys = []
|
||||||
chatmanager.scores = []
|
chatmanager.scores = []
|
||||||
|
|
||||||
@@ -80,6 +104,10 @@ class AgentNamespace(Namespace):
|
|||||||
backboard_manager.add_backboard(user_uuid, user_id, course_id, lesson_name, root_path=f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}')
|
backboard_manager.add_backboard(user_uuid, user_id, course_id, lesson_name, root_path=f'/home/{user_id}/{course_id}/{chapter_name}/{lesson_name}')
|
||||||
chatmanager.bb = backboard_manager.get_backboard(user_uuid)
|
chatmanager.bb = backboard_manager.get_backboard(user_uuid)
|
||||||
chatmanager.next_chapter()
|
chatmanager.next_chapter()
|
||||||
|
|
||||||
|
# 如果有保存的进度,需要跳过已经完成的章节
|
||||||
|
for _ in range(need_skip_chapters):
|
||||||
|
chatmanager.next_chapter()
|
||||||
self.chatmanager = chatmanager
|
self.chatmanager = chatmanager
|
||||||
self.ase_client = user_uuid2ase_client[user_uuid]
|
self.ase_client = user_uuid2ase_client[user_uuid]
|
||||||
user_uuid2ase_client[user_uuid].register_on_connect_entry(
|
user_uuid2ase_client[user_uuid].register_on_connect_entry(
|
||||||
@@ -221,7 +249,7 @@ class AgentNamespace(Namespace):
|
|||||||
if len(chatmanager.scores) >= chatmanager.chapter_chain_now:
|
if len(chatmanager.scores) >= chatmanager.chapter_chain_now:
|
||||||
chatmanager.scores[chatmanager.chapter_chain_now - 1] = data.get('data', None)
|
chatmanager.scores[chatmanager.chapter_chain_now - 1] = data.get('data', None)
|
||||||
else: chatmanager.scores.append(data.get('data', None))
|
else: chatmanager.scores.append(data.get('data', None))
|
||||||
user.set_course_progress(chatmanager.chat_historys, chatmanager.scores, chatmanager.material_id, chatmanager.chapter_name, chatmanager.lesson_name)
|
user.set_course_progress(chatmanager.chat_historys, chatmanager.scores, chatmanager.course_id, chatmanager.chapter_name, chatmanager.lesson_name)
|
||||||
chatmanager.load_next_chapter()
|
chatmanager.load_next_chapter()
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
chatmanager.ase_client.send_text("chapter-start", "")
|
chatmanager.ase_client.send_text("chapter-start", "")
|
||||||
@@ -241,10 +269,32 @@ class AgentNamespace(Namespace):
|
|||||||
|
|
||||||
|
|
||||||
def on_disconnect(self,data):
|
def on_disconnect(self,data):
|
||||||
|
print(f'AgentNamespace client disconnected, session user_uuid: {session.get("user_uuid", "unknown")}')
|
||||||
ex = current_app.extensions
|
ex = current_app.extensions
|
||||||
uuid = session.get('user_uuid')
|
uuid = session.get('user_uuid')
|
||||||
user_uuid2chatmanager = ex["user_uuid2chatmanager"]
|
user_uuid2chatmanager = ex.get("user_uuid2chatmanager", {})
|
||||||
|
|
||||||
|
# 保存学习进度到MongoDB
|
||||||
|
try:
|
||||||
|
# 获取用户ID
|
||||||
|
uuid2username = ex.get("uuid2username", {})
|
||||||
|
user_id = uuid2username.get(uuid, "unknown")
|
||||||
|
|
||||||
|
# 调用保存学习进度功能
|
||||||
|
chatmanager = user_uuid2chatmanager.get(uuid)
|
||||||
|
if chatmanager:
|
||||||
|
save_result = save_learning_progress(uuid, chatmanager, user_id)
|
||||||
|
print(f"保存学习进度结果: {save_result}, user_uuid={uuid}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"保存学习进度时发生异常: {str(e)}")
|
||||||
|
|
||||||
|
# 执行原有的断开连接逻辑
|
||||||
|
if uuid in user_uuid2chatmanager:
|
||||||
user_uuid2chatmanager[uuid].disconnect(uuid)
|
user_uuid2chatmanager[uuid].disconnect(uuid)
|
||||||
|
|
||||||
|
# 离开房间
|
||||||
|
if uuid:
|
||||||
|
leave_room(uuid, namespace='/agent')
|
||||||
|
|
||||||
print("VSCode client disconnected")
|
print("VSCode client disconnected")
|
||||||
print("Disconnect reason:"+str(data))
|
print("Disconnect reason:"+str(data))
|
||||||
@@ -105,29 +105,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
document.getElementById('chatbox').appendChild(requestMessage);
|
document.getElementById('chatbox').appendChild(requestMessage);
|
||||||
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
||||||
}
|
}
|
||||||
// for (let i = 0; i < data.length; i++) {
|
|
||||||
// const element = data[i]
|
|
||||||
// const requestMessage = document.createElement('div');
|
|
||||||
// requestMessage.className = 'chat-message server-message';
|
|
||||||
// const approveButton = document.createElement('button');
|
|
||||||
// approveButton.innerText = '批准';
|
|
||||||
// approveButton.data = element;
|
|
||||||
// approveButton.style.width = '100%';
|
|
||||||
// approveButton.style.borderRadius = '4px';
|
|
||||||
// approveButton.onclick = function(event) {
|
|
||||||
// console.log(element)
|
|
||||||
// socket.emit('message', JSON.stringify({type:'function', data:element}));
|
|
||||||
// event.currentTarget.disabled = true;
|
|
||||||
// event.currentTarget.style.backgroundColor = 'gray';
|
|
||||||
// event.currentTarget.style.color = 'white'; // 可选:设置文字颜色为白色,以便更清晰地显示
|
|
||||||
// event.currentTarget.style.border = 'none'; // 可选:去掉边框
|
|
||||||
// };
|
|
||||||
// // 将按钮添加到消息气泡中
|
|
||||||
// requestMessage.innerHTML = `<b>Function:</b> ${element.name}(${JSON.stringify(element.arguments)})<br>`;
|
|
||||||
// requestMessage.appendChild(approveButton);
|
|
||||||
// document.getElementById('chatbox').appendChild(requestMessage);
|
|
||||||
// document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
|
||||||
// }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
@@ -250,124 +228,9 @@ function sendMessage() {
|
|||||||
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
document.getElementById('chatbox').scrollTop = document.getElementById('chatbox').scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// let voice_stream = null;
|
|
||||||
// let audio_chunks = [];
|
|
||||||
// let isRecording = false;
|
|
||||||
// let recordingInterval = null;
|
|
||||||
// //鼠标点击终止事件
|
|
||||||
// function handleMouseClick(event) {
|
|
||||||
// event.stopPropagation();
|
|
||||||
// stopRecording();
|
|
||||||
// }
|
|
||||||
// //发送语音
|
|
||||||
// const startAudioRecording = async () => {
|
|
||||||
// try {
|
|
||||||
// const stream = await navigator.mediaDevices.getUserMedia({
|
|
||||||
// audio: {
|
|
||||||
// sampleRate: 16000,
|
|
||||||
// channelCount: 1,
|
|
||||||
// echoCancellation: true,
|
|
||||||
// noiseSuppression: true
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// const mediaRecorder = new MediaRecorder(stream);
|
|
||||||
// const audioContext = new AudioContext({
|
|
||||||
// sampleRate: 16000
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Load the AudioWorklet
|
|
||||||
// await audioContext.audioWorklet.addModule('/static/js/audio-processor.js');
|
|
||||||
|
|
||||||
// const source = audioContext.createMediaStreamSource(stream);
|
|
||||||
// const workletNode = new AudioWorkletNode(audioContext, 'audio-processor');
|
|
||||||
// let audioChunks = [];
|
|
||||||
|
|
||||||
// workletNode.port.onmessage = (e) => {
|
|
||||||
// audioChunks.push(new Float32Array(e.data));
|
|
||||||
|
|
||||||
// if (audioChunks.length >= 16) {
|
|
||||||
// // 合并所有音频块
|
|
||||||
// const totalLength = audioChunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
||||||
// const mergedData = new Float32Array(totalLength);
|
|
||||||
// let offset = 0;
|
|
||||||
// for (const chunk of audioChunks) {
|
|
||||||
// mergedData.set(chunk, offset);
|
|
||||||
// offset += chunk.length;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const audioMessage = {
|
|
||||||
// id: window.data.user_uuid,
|
|
||||||
// type: 'audio',
|
|
||||||
// data: mergedData,
|
|
||||||
// sampleRate: 16000,
|
|
||||||
// timestamp: Date.now()
|
|
||||||
// };
|
|
||||||
// socket.emit('voice', audioMessage);
|
|
||||||
// console.log("voice sended")
|
|
||||||
// audioChunks = [];
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// source.connect(workletNode);
|
|
||||||
// workletNode.connect(audioContext.destination);
|
|
||||||
|
|
||||||
// mediaRecorder.value = {
|
|
||||||
// stop: () => {
|
|
||||||
// source.disconnect();
|
|
||||||
// workletNode.disconnect();
|
|
||||||
// stream.getTracks().forEach(track => track.stop());
|
|
||||||
// audioContext.close();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// isRecording.value = true;
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error('Error starting audio recording:', error);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// function sendVoiceMessage() {
|
|
||||||
// startAudioRecording()
|
|
||||||
// //变换录音中图标
|
|
||||||
// document.getElementById('recordIcon').className = 'bi bi-record-circle';
|
|
||||||
// document.addEventListener('click', handleMouseClick);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //停止录音
|
|
||||||
// function stopRecording() {
|
|
||||||
// if (!isRecording) return;
|
|
||||||
// isRecording = false;
|
|
||||||
// //取消监听
|
|
||||||
// document.removeEventListener('click', handleMouseClick);
|
|
||||||
// //取消定时器
|
|
||||||
// if (recordingInterval) {
|
|
||||||
// clearInterval(recordingInterval);
|
|
||||||
// recordingInterval = null;
|
|
||||||
// }
|
|
||||||
// //变换图标
|
|
||||||
// document.getElementById('recordIcon').className = 'bi bi-mic';
|
|
||||||
// //发送剩下的数据
|
|
||||||
// if (audio_chunks.length > 0) {
|
|
||||||
// const merged_data = new Uint8Array(audio_chunks.reduce((acc, chunk) => acc + chunk.length, 0));
|
|
||||||
// let offset = 0;
|
|
||||||
// for (const chunk of audio_chunks) {
|
|
||||||
// merged_data.set(new Uint8Array(chunk), offset);
|
|
||||||
// offset += chunk.length;
|
|
||||||
// }
|
|
||||||
// socket.emit('VoiceMessage', merged_data, { binary: true });
|
|
||||||
// audio_chunks = [];
|
|
||||||
// }
|
|
||||||
// overlay.style.display = 'none';
|
|
||||||
// //关闭音频流
|
|
||||||
// if (voice_stream) {
|
|
||||||
// voice_stream.stop_stream();
|
|
||||||
// voice_stream.close();
|
|
||||||
// voice_stream = null;
|
|
||||||
// }
|
|
||||||
// if (p) {
|
|
||||||
// p.terminate();
|
|
||||||
// p = null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
//语音这一块//
|
//语音这一块//
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ region = ap-guangzhou
|
|||||||
[ASE_ENGINE]
|
[ASE_ENGINE]
|
||||||
url = https://asengine.net
|
url = https://asengine.net
|
||||||
namespace = /socket.io/7100eebf7a909e1ee3f4e06766f512f0358ecb6ac51d5434c17d31dd70890dc1
|
namespace = /socket.io/7100eebf7a909e1ee3f4e06766f512f0358ecb6ac51d5434c17d31dd70890dc1
|
||||||
|
url_token = 7100eebf7a909e1ee3f4e06766f512f0358ecb6ac51d5434c17d31dd70890dc1
|
||||||
|
|||||||
Reference in New Issue
Block a user