Reworking auto type system
This commit is contained in:
parent
4b33b40637
commit
d679c8cecf
@ -1,250 +1,16 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "FastAPI",
|
"title": "AI Voice Bot Server (Refactored)",
|
||||||
"version": "0.1.0"
|
"description": "WebRTC voice chat server with modular architecture",
|
||||||
|
"version": "2.0.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/ai-voicebot/api/admin/names": {
|
"/ai-voicebot/api/system/health": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "Admin List Names",
|
"summary": "System Health",
|
||||||
"operationId": "admin_list_names_ai_voicebot_api_admin_names_get",
|
"description": "System health check showing manager status and enhanced monitoring",
|
||||||
"responses": {
|
"operationId": "system_health_ai_voicebot_api_system_health_get",
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AdminNamesResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/ai-voicebot/api/admin/set_password": {
|
|
||||||
"post": {
|
|
||||||
"summary": "Admin Set Password",
|
|
||||||
"operationId": "admin_set_password_ai_voicebot_api_admin_set_password_post",
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AdminSetPassword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AdminActionResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/ai-voicebot/api/admin/clear_password": {
|
|
||||||
"post": {
|
|
||||||
"summary": "Admin Clear Password",
|
|
||||||
"operationId": "admin_clear_password_ai_voicebot_api_admin_clear_password_post",
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AdminClearPassword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AdminActionResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/ai-voicebot/api/health": {
|
|
||||||
"get": {
|
|
||||||
"summary": "Health",
|
|
||||||
"operationId": "health_ai_voicebot_api_health_get",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HealthResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/ai-voicebot/api/session": {
|
|
||||||
"get": {
|
|
||||||
"summary": "Session",
|
|
||||||
"operationId": "session_ai_voicebot_api_session_get",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "session_id",
|
|
||||||
"in": "cookie",
|
|
||||||
"required": false,
|
|
||||||
"schema": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Session Id"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/SessionResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/ai-voicebot/api/lobby": {
|
|
||||||
"get": {
|
|
||||||
"summary": "Get Lobbies",
|
|
||||||
"operationId": "get_lobbies_ai_voicebot_api_lobby_get",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/LobbiesResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/ai-voicebot/api/lobby/{session_id}": {
|
|
||||||
"post": {
|
|
||||||
"summary": "Lobby Create",
|
|
||||||
"operationId": "lobby_create_ai_voicebot_api_lobby__session_id__post",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "session_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Session Id"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/LobbyCreateRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/LobbyCreateResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/ai-voicebot/{path}": {
|
|
||||||
"put": {
|
|
||||||
"summary": "Proxy Static",
|
|
||||||
"operationId": "proxy_static_ai_voicebot__path__put",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successful Response",
|
"description": "Successful Response",
|
||||||
@ -253,528 +19,8 @@
|
|||||||
"schema": {}
|
"schema": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"head": {
|
|
||||||
"summary": "Proxy Static",
|
|
||||||
"operationId": "proxy_static_ai_voicebot__path__put",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"summary": "Proxy Static",
|
|
||||||
"operationId": "proxy_static_ai_voicebot__path__put",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"post": {
|
|
||||||
"summary": "Proxy Static",
|
|
||||||
"operationId": "proxy_static_ai_voicebot__path__put",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"summary": "Proxy Static",
|
|
||||||
"operationId": "proxy_static_ai_voicebot__path__put",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"get": {
|
|
||||||
"summary": "Proxy Static",
|
|
||||||
"operationId": "proxy_static_ai_voicebot__path__put",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"patch": {
|
|
||||||
"summary": "Proxy Static",
|
|
||||||
"operationId": "proxy_static_ai_voicebot__path__put",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successful Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"422": {
|
|
||||||
"description": "Validation Error",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/HTTPValidationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"schemas": {
|
|
||||||
"AdminActionResponse": {
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ok",
|
|
||||||
"not_found"
|
|
||||||
],
|
|
||||||
"title": "Status"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"status",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"title": "AdminActionResponse",
|
|
||||||
"description": "Response for admin actions"
|
|
||||||
},
|
|
||||||
"AdminClearPassword": {
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"title": "AdminClearPassword",
|
|
||||||
"description": "Request model for clearing admin password"
|
|
||||||
},
|
|
||||||
"AdminNamesResponse": {
|
|
||||||
"properties": {
|
|
||||||
"name_passwords": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"$ref": "#/components/schemas/NamePasswordRecord"
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"title": "Name Passwords"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"name_passwords"
|
|
||||||
],
|
|
||||||
"title": "AdminNamesResponse",
|
|
||||||
"description": "Response for admin names endpoint"
|
|
||||||
},
|
|
||||||
"AdminSetPassword": {
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name"
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Password"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"name",
|
|
||||||
"password"
|
|
||||||
],
|
|
||||||
"title": "AdminSetPassword",
|
|
||||||
"description": "Request model for setting admin password"
|
|
||||||
},
|
|
||||||
"HTTPValidationError": {
|
|
||||||
"properties": {
|
|
||||||
"detail": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/ValidationError"
|
|
||||||
},
|
|
||||||
"type": "array",
|
|
||||||
"title": "Detail"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"title": "HTTPValidationError"
|
|
||||||
},
|
|
||||||
"HealthResponse": {
|
|
||||||
"properties": {
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Status"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"status"
|
|
||||||
],
|
|
||||||
"title": "HealthResponse",
|
|
||||||
"description": "Health check response"
|
|
||||||
},
|
|
||||||
"LobbiesResponse": {
|
|
||||||
"properties": {
|
|
||||||
"lobbies": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/LobbyListItem"
|
|
||||||
},
|
|
||||||
"type": "array",
|
|
||||||
"title": "Lobbies"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"lobbies"
|
|
||||||
],
|
|
||||||
"title": "LobbiesResponse",
|
|
||||||
"description": "Response containing list of lobbies"
|
|
||||||
},
|
|
||||||
"LobbyCreateData": {
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name"
|
|
||||||
},
|
|
||||||
"private": {
|
|
||||||
"type": "boolean",
|
|
||||||
"title": "Private",
|
|
||||||
"default": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"title": "LobbyCreateData",
|
|
||||||
"description": "Data for lobby creation"
|
|
||||||
},
|
|
||||||
"LobbyCreateRequest": {
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "lobby_create",
|
|
||||||
"title": "Type"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/components/schemas/LobbyCreateData"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"type",
|
|
||||||
"data"
|
|
||||||
],
|
|
||||||
"title": "LobbyCreateRequest",
|
|
||||||
"description": "Request for creating a lobby"
|
|
||||||
},
|
|
||||||
"LobbyCreateResponse": {
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"const": "lobby_created",
|
|
||||||
"title": "Type"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/components/schemas/LobbyModel"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"type",
|
|
||||||
"data"
|
|
||||||
],
|
|
||||||
"title": "LobbyCreateResponse",
|
|
||||||
"description": "Response for lobby creation"
|
|
||||||
},
|
|
||||||
"LobbyListItem": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Id"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"title": "LobbyListItem",
|
|
||||||
"description": "Lobby item for list responses"
|
|
||||||
},
|
|
||||||
"LobbyModel": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Id"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name"
|
|
||||||
},
|
|
||||||
"private": {
|
|
||||||
"type": "boolean",
|
|
||||||
"title": "Private",
|
|
||||||
"default": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"title": "LobbyModel",
|
|
||||||
"description": "Core lobby model used across components"
|
|
||||||
},
|
|
||||||
"NamePasswordRecord": {
|
|
||||||
"properties": {
|
|
||||||
"salt": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Salt"
|
|
||||||
},
|
|
||||||
"hash": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Hash"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"salt",
|
|
||||||
"hash"
|
|
||||||
],
|
|
||||||
"title": "NamePasswordRecord",
|
|
||||||
"description": "Password hash record for reserved names"
|
|
||||||
},
|
|
||||||
"SessionResponse": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Id"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Name"
|
|
||||||
},
|
|
||||||
"lobbies": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/LobbyModel"
|
|
||||||
},
|
|
||||||
"type": "array",
|
|
||||||
"title": "Lobbies"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"lobbies"
|
|
||||||
],
|
|
||||||
"title": "SessionResponse",
|
|
||||||
"description": "Session response model"
|
|
||||||
},
|
|
||||||
"ValidationError": {
|
|
||||||
"properties": {
|
|
||||||
"loc": {
|
|
||||||
"items": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"type": "array",
|
|
||||||
"title": "Location"
|
|
||||||
},
|
|
||||||
"msg": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Message"
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Error Type"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"loc",
|
|
||||||
"msg",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"title": "ValidationError"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ type User = {
|
|||||||
live: boolean;
|
live: boolean;
|
||||||
local: boolean /* Client side variable */;
|
local: boolean /* Client side variable */;
|
||||||
protected?: boolean;
|
protected?: boolean;
|
||||||
is_bot?: boolean;
|
|
||||||
has_media?: boolean; // Whether this user provides audio/video streams
|
has_media?: boolean; // Whether this user provides audio/video streams
|
||||||
bot_run_id?: string;
|
bot_run_id?: string;
|
||||||
bot_provider_id?: string;
|
bot_provider_id?: string;
|
||||||
@ -49,7 +48,7 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
|||||||
const apiClient = new ApiClient();
|
const apiClient = new ApiClient();
|
||||||
|
|
||||||
const handleBotLeave = async (user: User) => {
|
const handleBotLeave = async (user: User) => {
|
||||||
if (!user.is_bot || !user.bot_instance_id) return;
|
if (!user.bot_instance_id) return;
|
||||||
|
|
||||||
setLeavingBots((prev) => new Set(prev).add(user.session_id));
|
setLeavingBots((prev) => new Set(prev).add(user.session_id));
|
||||||
|
|
||||||
@ -168,13 +167,13 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
|
|||||||
🔒
|
🔒
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{user.is_bot && (
|
{user.bot_instance_id && (
|
||||||
<div style={{ marginLeft: 8, fontSize: "0.8em", color: "#00a" }} title="This is a bot">
|
<div style={{ marginLeft: 8, fontSize: "0.8em", color: "#00a" }} title="This is a bot">
|
||||||
🤖
|
🤖
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{user.is_bot && (
|
{user.bot_instance_id && (
|
||||||
<Box style={{ display: "flex-wrap", gap: "4px", border: "3px solid magenta" }}>
|
<Box style={{ display: "flex-wrap", gap: "4px", border: "3px solid magenta" }}>
|
||||||
{user.bot_run_id && (
|
{user.bot_run_id && (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@ -248,6 +248,11 @@ export class ApiClient {
|
|||||||
// Auto-generated endpoints will be added here by update-api-client.js
|
// Auto-generated endpoints will be added here by update-api-client.js
|
||||||
// DO NOT MANUALLY EDIT BELOW THIS LINE
|
// DO NOT MANUALLY EDIT BELOW THIS LINE
|
||||||
|
|
||||||
|
// Auto-generated endpoints
|
||||||
|
async systemHealth(): Promise<any> {
|
||||||
|
return this.request<any>(this.getApiPath("/ai-voicebot/api/system/health"), { method: "GET" });
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-generated endpoints
|
// Auto-generated endpoints
|
||||||
async lobbyCreate(session_id: string, data: any): Promise<any> {
|
async lobbyCreate(session_id: string, data: any): Promise<any> {
|
||||||
return this.request<any>(this.getApiPath(`/ai-voicebot/api/lobby/${session_id}`), { method: "POST", body: data });
|
return this.request<any>(this.getApiPath(`/ai-voicebot/api/lobby/${session_id}`), { method: "POST", body: data });
|
||||||
@ -282,6 +287,7 @@ class ApiEvolutionChecker {
|
|||||||
'GET:/ai-voicebot/api/health',
|
'GET:/ai-voicebot/api/health',
|
||||||
'GET:/ai-voicebot/api/lobby',
|
'GET:/ai-voicebot/api/lobby',
|
||||||
'GET:/ai-voicebot/api/session',
|
'GET:/ai-voicebot/api/session',
|
||||||
|
'GET:/ai-voicebot/api/system/health',
|
||||||
'POST:/ai-voicebot/api/admin/clear_password',
|
'POST:/ai-voicebot/api/admin/clear_password',
|
||||||
'POST:/ai-voicebot/api/admin/set_password',
|
'POST:/ai-voicebot/api/admin/set_password',
|
||||||
'POST:/ai-voicebot/api/lobby/{sessionId}',
|
'POST:/ai-voicebot/api/lobby/{sessionId}',
|
||||||
|
@ -34,13 +34,7 @@ export class AdvancedApiEvolutionChecker {
|
|||||||
// a list that should be updated when new endpoints are added to the schema.
|
// a list that should be updated when new endpoints are added to the schema.
|
||||||
// This list is automatically updated by the update-api-client.js script
|
// This list is automatically updated by the update-api-client.js script
|
||||||
const knownSchemaEndpoints = [
|
const knownSchemaEndpoints = [
|
||||||
{ path: '/ai-voicebot/api/admin/names', method: 'GET', operationId: 'admin_list_names_ai_voicebot_api_admin_names_get' },
|
{ path: '/ai-voicebot/api/system/health', method: 'GET', operationId: 'system_health_ai_voicebot_api_system_health_get' }
|
||||||
{ path: '/ai-voicebot/api/admin/set_password', method: 'POST', operationId: 'admin_set_password_ai_voicebot_api_admin_set_password_post' },
|
|
||||||
{ path: '/ai-voicebot/api/admin/clear_password', method: 'POST', operationId: 'admin_clear_password_ai_voicebot_api_admin_clear_password_post' },
|
|
||||||
{ path: '/ai-voicebot/api/health', method: 'GET', operationId: 'health_ai_voicebot_api_health_get' },
|
|
||||||
{ path: '/ai-voicebot/api/session', method: 'GET', operationId: 'session_ai_voicebot_api_session_get' },
|
|
||||||
{ path: '/ai-voicebot/api/lobby', method: 'GET', operationId: 'get_lobbies_ai_voicebot_api_lobby_get' },
|
|
||||||
{ path: '/ai-voicebot/api/lobby/{session_id}', method: 'POST', operationId: 'lobby_create_ai_voicebot_api_lobby__session_id__post' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Get implemented endpoints from ApiClient
|
// Get implemented endpoints from ApiClient
|
||||||
@ -68,7 +62,8 @@ export class AdvancedApiEvolutionChecker {
|
|||||||
'POST:/ai-voicebot/api/lobby/{sessionId}',
|
'POST:/ai-voicebot/api/lobby/{sessionId}',
|
||||||
'GET:/ai-voicebot/api/bots/providers',
|
'GET:/ai-voicebot/api/bots/providers',
|
||||||
'GET:/ai-voicebot/api/bots',
|
'GET:/ai-voicebot/api/bots',
|
||||||
'POST:/ai-voicebot/api/lobby/{session_id}'
|
'POST:/ai-voicebot/api/lobby/{session_id}',
|
||||||
|
'GET:/ai-voicebot/api/system/health'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,218 +5,18 @@
|
|||||||
|
|
||||||
|
|
||||||
export interface paths {
|
export interface paths {
|
||||||
"/ai-voicebot/api/admin/names": {
|
"/ai-voicebot/api/system/health": {
|
||||||
/** Admin List Names */
|
/**
|
||||||
get: operations["admin_list_names_ai_voicebot_api_admin_names_get"];
|
* System Health
|
||||||
};
|
* @description System health check showing manager status and enhanced monitoring
|
||||||
"/ai-voicebot/api/admin/set_password": {
|
*/
|
||||||
/** Admin Set Password */
|
get: operations["system_health_ai_voicebot_api_system_health_get"];
|
||||||
post: operations["admin_set_password_ai_voicebot_api_admin_set_password_post"];
|
|
||||||
};
|
|
||||||
"/ai-voicebot/api/admin/clear_password": {
|
|
||||||
/** Admin Clear Password */
|
|
||||||
post: operations["admin_clear_password_ai_voicebot_api_admin_clear_password_post"];
|
|
||||||
};
|
|
||||||
"/ai-voicebot/api/health": {
|
|
||||||
/** Health */
|
|
||||||
get: operations["health_ai_voicebot_api_health_get"];
|
|
||||||
};
|
|
||||||
"/ai-voicebot/api/session": {
|
|
||||||
/** Session */
|
|
||||||
get: operations["session_ai_voicebot_api_session_get"];
|
|
||||||
};
|
|
||||||
"/ai-voicebot/api/lobby": {
|
|
||||||
/** Get Lobbies */
|
|
||||||
get: operations["get_lobbies_ai_voicebot_api_lobby_get"];
|
|
||||||
};
|
|
||||||
"/ai-voicebot/api/lobby/{session_id}": {
|
|
||||||
/** Lobby Create */
|
|
||||||
post: operations["lobby_create_ai_voicebot_api_lobby__session_id__post"];
|
|
||||||
};
|
|
||||||
"/ai-voicebot/{path}": {
|
|
||||||
/** Proxy Static */
|
|
||||||
get: operations["proxy_static_ai_voicebot__path__put"];
|
|
||||||
/** Proxy Static */
|
|
||||||
put: operations["proxy_static_ai_voicebot__path__put"];
|
|
||||||
/** Proxy Static */
|
|
||||||
post: operations["proxy_static_ai_voicebot__path__put"];
|
|
||||||
/** Proxy Static */
|
|
||||||
delete: operations["proxy_static_ai_voicebot__path__put"];
|
|
||||||
/** Proxy Static */
|
|
||||||
options: operations["proxy_static_ai_voicebot__path__put"];
|
|
||||||
/** Proxy Static */
|
|
||||||
head: operations["proxy_static_ai_voicebot__path__put"];
|
|
||||||
/** Proxy Static */
|
|
||||||
patch: operations["proxy_static_ai_voicebot__path__put"];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
|
|
||||||
export interface components {
|
export type components = Record<string, never>;
|
||||||
schemas: {
|
|
||||||
/**
|
|
||||||
* AdminActionResponse
|
|
||||||
* @description Response for admin actions
|
|
||||||
*/
|
|
||||||
AdminActionResponse: {
|
|
||||||
/**
|
|
||||||
* Status
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
status: "ok" | "not_found";
|
|
||||||
/** Name */
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* AdminClearPassword
|
|
||||||
* @description Request model for clearing admin password
|
|
||||||
*/
|
|
||||||
AdminClearPassword: {
|
|
||||||
/** Name */
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* AdminNamesResponse
|
|
||||||
* @description Response for admin names endpoint
|
|
||||||
*/
|
|
||||||
AdminNamesResponse: {
|
|
||||||
/** Name Passwords */
|
|
||||||
name_passwords: {
|
|
||||||
[key: string]: components["schemas"]["NamePasswordRecord"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* AdminSetPassword
|
|
||||||
* @description Request model for setting admin password
|
|
||||||
*/
|
|
||||||
AdminSetPassword: {
|
|
||||||
/** Name */
|
|
||||||
name: string;
|
|
||||||
/** Password */
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
/** HTTPValidationError */
|
|
||||||
HTTPValidationError: {
|
|
||||||
/** Detail */
|
|
||||||
detail?: components["schemas"]["ValidationError"][];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* HealthResponse
|
|
||||||
* @description Health check response
|
|
||||||
*/
|
|
||||||
HealthResponse: {
|
|
||||||
/** Status */
|
|
||||||
status: string;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* LobbiesResponse
|
|
||||||
* @description Response containing list of lobbies
|
|
||||||
*/
|
|
||||||
LobbiesResponse: {
|
|
||||||
/** Lobbies */
|
|
||||||
lobbies: components["schemas"]["LobbyListItem"][];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* LobbyCreateData
|
|
||||||
* @description Data for lobby creation
|
|
||||||
*/
|
|
||||||
LobbyCreateData: {
|
|
||||||
/** Name */
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* Private
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
private?: boolean;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* LobbyCreateRequest
|
|
||||||
* @description Request for creating a lobby
|
|
||||||
*/
|
|
||||||
LobbyCreateRequest: {
|
|
||||||
/**
|
|
||||||
* Type
|
|
||||||
* @constant
|
|
||||||
*/
|
|
||||||
type: "lobby_create";
|
|
||||||
data: components["schemas"]["LobbyCreateData"];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* LobbyCreateResponse
|
|
||||||
* @description Response for lobby creation
|
|
||||||
*/
|
|
||||||
LobbyCreateResponse: {
|
|
||||||
/**
|
|
||||||
* Type
|
|
||||||
* @constant
|
|
||||||
*/
|
|
||||||
type: "lobby_created";
|
|
||||||
data: components["schemas"]["LobbyModel"];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* LobbyListItem
|
|
||||||
* @description Lobby item for list responses
|
|
||||||
*/
|
|
||||||
LobbyListItem: {
|
|
||||||
/** Id */
|
|
||||||
id: string;
|
|
||||||
/** Name */
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* LobbyModel
|
|
||||||
* @description Core lobby model used across components
|
|
||||||
*/
|
|
||||||
LobbyModel: {
|
|
||||||
/** Id */
|
|
||||||
id: string;
|
|
||||||
/** Name */
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* Private
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
private?: boolean;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* NamePasswordRecord
|
|
||||||
* @description Password hash record for reserved names
|
|
||||||
*/
|
|
||||||
NamePasswordRecord: {
|
|
||||||
/** Salt */
|
|
||||||
salt: string;
|
|
||||||
/** Hash */
|
|
||||||
hash: string;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* SessionResponse
|
|
||||||
* @description Session response model
|
|
||||||
*/
|
|
||||||
SessionResponse: {
|
|
||||||
/** Id */
|
|
||||||
id: string;
|
|
||||||
/** Name */
|
|
||||||
name: string;
|
|
||||||
/** Lobbies */
|
|
||||||
lobbies: components["schemas"]["LobbyModel"][];
|
|
||||||
};
|
|
||||||
/** ValidationError */
|
|
||||||
ValidationError: {
|
|
||||||
/** Location */
|
|
||||||
loc: (string | number)[];
|
|
||||||
/** Message */
|
|
||||||
msg: string;
|
|
||||||
/** Error Type */
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: never;
|
|
||||||
parameters: never;
|
|
||||||
requestBodies: never;
|
|
||||||
headers: never;
|
|
||||||
pathItems: never;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type $defs = Record<string, never>;
|
export type $defs = Record<string, never>;
|
||||||
|
|
||||||
@ -224,139 +24,11 @@ export type external = Record<string, never>;
|
|||||||
|
|
||||||
export interface operations {
|
export interface operations {
|
||||||
|
|
||||||
/** Admin List Names */
|
/**
|
||||||
admin_list_names_ai_voicebot_api_admin_names_get: {
|
* System Health
|
||||||
responses: {
|
* @description System health check showing manager status and enhanced monitoring
|
||||||
/** @description Successful Response */
|
*/
|
||||||
200: {
|
system_health_ai_voicebot_api_system_health_get: {
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["AdminNamesResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Admin Set Password */
|
|
||||||
admin_set_password_ai_voicebot_api_admin_set_password_post: {
|
|
||||||
requestBody: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["AdminSetPassword"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description Successful Response */
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["AdminActionResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Validation Error */
|
|
||||||
422: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["HTTPValidationError"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Admin Clear Password */
|
|
||||||
admin_clear_password_ai_voicebot_api_admin_clear_password_post: {
|
|
||||||
requestBody: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["AdminClearPassword"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description Successful Response */
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["AdminActionResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Validation Error */
|
|
||||||
422: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["HTTPValidationError"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Health */
|
|
||||||
health_ai_voicebot_api_health_get: {
|
|
||||||
responses: {
|
|
||||||
/** @description Successful Response */
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["HealthResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Session */
|
|
||||||
session_ai_voicebot_api_session_get: {
|
|
||||||
parameters: {
|
|
||||||
cookie?: {
|
|
||||||
session_id?: string | null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description Successful Response */
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["SessionResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Validation Error */
|
|
||||||
422: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["HTTPValidationError"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Get Lobbies */
|
|
||||||
get_lobbies_ai_voicebot_api_lobby_get: {
|
|
||||||
responses: {
|
|
||||||
/** @description Successful Response */
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["LobbiesResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Lobby Create */
|
|
||||||
lobby_create_ai_voicebot_api_lobby__session_id__post: {
|
|
||||||
parameters: {
|
|
||||||
path: {
|
|
||||||
session_id: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
requestBody: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["LobbyCreateRequest"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description Successful Response */
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["LobbyCreateResponse"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Validation Error */
|
|
||||||
422: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["HTTPValidationError"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** Proxy Static */
|
|
||||||
proxy_static_ai_voicebot__path__put: {
|
|
||||||
parameters: {
|
|
||||||
path: {
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
responses: {
|
||||||
/** @description Successful Response */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
@ -364,12 +36,6 @@ export interface operations {
|
|||||||
"application/json": unknown;
|
"application/json": unknown;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Validation Error */
|
|
||||||
422: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["HTTPValidationError"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ cd "$(dirname "$0")"
|
|||||||
|
|
||||||
echo "📋 Step 1: Generating OpenAPI schema from FastAPI server..."
|
echo "📋 Step 1: Generating OpenAPI schema from FastAPI server..."
|
||||||
docker compose exec server uv run python3 generate_schema_simple.py
|
docker compose exec server uv run python3 generate_schema_simple.py
|
||||||
|
docker compose cp server:/client/openapi-schema.json ./client/openapi-schema.json
|
||||||
|
|
||||||
echo "📋 Step 2: Ensuring frontend container is running..."
|
echo "📋 Step 2: Ensuring frontend container is running..."
|
||||||
docker compose up -d client
|
docker compose up -d client
|
||||||
|
@ -45,8 +45,8 @@ class SessionAPI:
|
|||||||
name=session.name or "",
|
name=session.name or "",
|
||||||
lobbies=[], # New sessions start with no lobbies
|
lobbies=[], # New sessions start with no lobbies
|
||||||
protected=False,
|
protected=False,
|
||||||
is_bot=session.is_bot,
|
|
||||||
has_media=session.has_media,
|
has_media=session.has_media,
|
||||||
bot_run_id=session.bot_run_id,
|
bot_run_id=session.bot_run_id,
|
||||||
bot_provider_id=session.bot_provider_id,
|
bot_provider_id=session.bot_provider_id,
|
||||||
|
bot_instance_id=session.bot_instance_id,
|
||||||
)
|
)
|
||||||
|
@ -31,7 +31,8 @@ from shared.models import (
|
|||||||
BotJoinPayload,
|
BotJoinPayload,
|
||||||
BotInstanceModel,
|
BotInstanceModel,
|
||||||
)
|
)
|
||||||
|
from core.session_manager import SessionManager
|
||||||
|
from core.lobby_manager import LobbyManager
|
||||||
|
|
||||||
class BotProviderConfig:
|
class BotProviderConfig:
|
||||||
"""Configuration class for bot provider management"""
|
"""Configuration class for bot provider management"""
|
||||||
@ -180,26 +181,36 @@ class BotManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error fetching bots from provider {provider.name}: {e}")
|
logger.error(f"Error fetching bots from provider {provider.name}: {e}")
|
||||||
return BotProviderBotsResponse(bots=[])
|
return BotProviderBotsResponse(bots=[])
|
||||||
|
|
||||||
async def request_bot_join(self, bot_name: str, request: BotJoinLobbyRequest, session_manager, lobby_manager) -> BotJoinLobbyResponse:
|
async def request_bot_join(
|
||||||
|
self,
|
||||||
|
bot_name: str,
|
||||||
|
request: BotJoinLobbyRequest,
|
||||||
|
session_manager: SessionManager,
|
||||||
|
lobby_manager: LobbyManager,
|
||||||
|
) -> BotJoinLobbyResponse:
|
||||||
"""Request a bot to join a specific lobby"""
|
"""Request a bot to join a specific lobby"""
|
||||||
|
|
||||||
# Find which provider has this bot and determine its media capability
|
# Find which provider has this bot and determine its media capability
|
||||||
target_provider_id = request.provider_id
|
target_provider_id = request.provider_id
|
||||||
bot_has_media = False
|
bot_has_media = False
|
||||||
|
|
||||||
if not target_provider_id:
|
if not target_provider_id:
|
||||||
# Auto-discover provider for this bot
|
# Auto-discover provider for this bot
|
||||||
with self.lock:
|
with self.lock:
|
||||||
providers_copy = dict(self.bot_providers.items())
|
providers_copy = dict(self.bot_providers.items())
|
||||||
|
|
||||||
for provider_id, provider in providers_copy.items():
|
for provider_id, provider in providers_copy.items():
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(f"{provider.base_url}/bots", timeout=5.0)
|
response = await client.get(
|
||||||
|
f"{provider.base_url}/bots", timeout=5.0
|
||||||
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# Use Pydantic model to validate the response
|
# Use Pydantic model to validate the response
|
||||||
bots_response = BotProviderBotsResponse.model_validate(response.json())
|
bots_response = BotProviderBotsResponse.model_validate(
|
||||||
|
response.json()
|
||||||
|
)
|
||||||
# Look for the bot by name
|
# Look for the bot by name
|
||||||
for bot_info in bots_response.bots:
|
for bot_info in bots_response.bots:
|
||||||
if bot_info.name == bot_name:
|
if bot_info.name == bot_name:
|
||||||
@ -217,10 +228,14 @@ class BotManager:
|
|||||||
provider = self.bot_providers[target_provider_id]
|
provider = self.bot_providers[target_provider_id]
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(f"{provider.base_url}/bots", timeout=5.0)
|
response = await client.get(
|
||||||
|
f"{provider.base_url}/bots", timeout=5.0
|
||||||
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# Use Pydantic model to validate the response
|
# Use Pydantic model to validate the response
|
||||||
bots_response = BotProviderBotsResponse.model_validate(response.json())
|
bots_response = BotProviderBotsResponse.model_validate(
|
||||||
|
response.json()
|
||||||
|
)
|
||||||
# Look for the bot by name
|
# Look for the bot by name
|
||||||
for bot_info in bots_response.bots:
|
for bot_info in bots_response.bots:
|
||||||
if bot_info.name == bot_name:
|
if bot_info.name == bot_name:
|
||||||
@ -232,7 +247,7 @@ class BotManager:
|
|||||||
|
|
||||||
if not target_provider_id:
|
if not target_provider_id:
|
||||||
raise ValueError("Bot or provider not found")
|
raise ValueError("Bot or provider not found")
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if target_provider_id not in self.bot_providers:
|
if target_provider_id not in self.bot_providers:
|
||||||
raise ValueError("Provider not found")
|
raise ValueError("Provider not found")
|
||||||
@ -245,10 +260,15 @@ class BotManager:
|
|||||||
|
|
||||||
# Create a session for the bot
|
# Create a session for the bot
|
||||||
bot_session_id = secrets.token_hex(16)
|
bot_session_id = secrets.token_hex(16)
|
||||||
|
bot_instance_id = str(uuid.uuid4())
|
||||||
|
|
||||||
# Create the Session object for the bot
|
# Create the Session object for the bot
|
||||||
bot_session = session_manager.get_or_create_session(bot_session_id, is_bot=True, has_media=bot_has_media)
|
bot_session = session_manager.get_or_create_session(
|
||||||
logger.info(f"Created bot session for: {bot_session.getName()} (has_media={bot_has_media})")
|
bot_session_id, bot_instance_id=bot_instance_id, has_media=bot_has_media
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Created bot session for: {bot_session.getName()} (has_media={bot_has_media})"
|
||||||
|
)
|
||||||
|
|
||||||
# Determine server URL for the bot to connect back to
|
# Determine server URL for the bot to connect back to
|
||||||
# Use the server's public URL or construct from request
|
# Use the server's public URL or construct from request
|
||||||
@ -282,7 +302,9 @@ class BotManager:
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# Use Pydantic model to parse and validate response
|
# Use Pydantic model to parse and validate response
|
||||||
try:
|
try:
|
||||||
join_response = BotProviderJoinResponse.model_validate(response.json())
|
join_response = BotProviderJoinResponse.model_validate(
|
||||||
|
response.json()
|
||||||
|
)
|
||||||
run_id = join_response.run_id
|
run_id = join_response.run_id
|
||||||
|
|
||||||
# Update bot session with run and provider information
|
# Update bot session with run and provider information
|
||||||
@ -291,7 +313,6 @@ class BotManager:
|
|||||||
bot_session.bot_provider_id = target_provider_id
|
bot_session.bot_provider_id = target_provider_id
|
||||||
|
|
||||||
# Create a unique bot instance ID and track the bot instance
|
# Create a unique bot instance ID and track the bot instance
|
||||||
bot_instance_id = str(uuid.uuid4())
|
|
||||||
bot_instance = BotInstanceModel(
|
bot_instance = BotInstanceModel(
|
||||||
bot_instance_id=bot_instance_id,
|
bot_instance_id=bot_instance_id,
|
||||||
bot_name=bot_name,
|
bot_name=bot_name,
|
||||||
@ -324,9 +345,13 @@ class BotManager:
|
|||||||
)
|
)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
logger.error(f"Invalid response from bot provider: {e}")
|
logger.error(f"Invalid response from bot provider: {e}")
|
||||||
raise ValueError(f"Bot provider returned invalid response: {str(e)}")
|
raise ValueError(
|
||||||
|
f"Bot provider returned invalid response: {str(e)}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Bot provider returned error: HTTP {response.status_code}: {response.text}")
|
logger.error(
|
||||||
|
f"Bot provider returned error: HTTP {response.status_code}: {response.text}"
|
||||||
|
)
|
||||||
raise ValueError(f"Bot provider error: {response.status_code}")
|
raise ValueError(f"Bot provider error: {response.status_code}")
|
||||||
|
|
||||||
except httpx.TimeoutException:
|
except httpx.TimeoutException:
|
||||||
@ -334,8 +359,10 @@ class BotManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error requesting bot join: {e}")
|
logger.error(f"Error requesting bot join: {e}")
|
||||||
raise ValueError(f"Internal server error: {str(e)}")
|
raise ValueError(f"Internal server error: {str(e)}")
|
||||||
|
|
||||||
async def request_bot_leave(self, request: BotLeaveLobbyRequest, session_manager) -> BotLeaveLobbyResponse:
|
async def request_bot_leave(
|
||||||
|
self, request: BotLeaveLobbyRequest, session_manager: SessionManager
|
||||||
|
) -> BotLeaveLobbyResponse:
|
||||||
"""Request a bot to leave from all lobbies and disconnect"""
|
"""Request a bot to leave from all lobbies and disconnect"""
|
||||||
|
|
||||||
# Find the bot instance
|
# Find the bot instance
|
||||||
@ -349,7 +376,7 @@ class BotManager:
|
|||||||
if not bot_session:
|
if not bot_session:
|
||||||
raise ValueError("Bot session not found")
|
raise ValueError("Bot session not found")
|
||||||
|
|
||||||
if not bot_session.is_bot:
|
if not bot_session.bot_instance_id:
|
||||||
raise ValueError("Session is not a bot")
|
raise ValueError("Session is not a bot")
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -412,14 +439,14 @@ class BotManager:
|
|||||||
run_id=bot_instance.run_id,
|
run_id=bot_instance.run_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_bot_instance(self, bot_instance_id: str) -> dict:
|
async def get_bot_instance(self, bot_instance_id: str) -> BotInstanceModel:
|
||||||
"""Get information about a specific bot instance"""
|
"""Get information about a specific bot instance"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if bot_instance_id not in self.bot_instances:
|
if bot_instance_id not in self.bot_instances:
|
||||||
raise ValueError("Bot instance not found")
|
raise ValueError("Bot instance not found")
|
||||||
bot_instance = self.bot_instances[bot_instance_id]
|
bot_instance = self.bot_instances[bot_instance_id]
|
||||||
|
|
||||||
return bot_instance.model_dump()
|
return bot_instance
|
||||||
|
|
||||||
def get_bot_instance_id_by_session_id(self, session_id: str) -> Optional[str]:
|
def get_bot_instance_id_by_session_id(self, session_id: str) -> Optional[str]:
|
||||||
"""Get bot_instance_id by session_id"""
|
"""Get bot_instance_id by session_id"""
|
||||||
|
@ -84,11 +84,13 @@ class Lobby:
|
|||||||
name=s.name,
|
name=s.name,
|
||||||
live=True if s.ws else False,
|
live=True if s.ws else False,
|
||||||
session_id=s.id,
|
session_id=s.id,
|
||||||
protected=True if s.name and self._is_name_protected(s.name) else False,
|
protected=True
|
||||||
is_bot=s.is_bot,
|
if s.name and self._is_name_protected(s.name)
|
||||||
|
else False,
|
||||||
has_media=s.has_media,
|
has_media=s.has_media,
|
||||||
bot_run_id=s.bot_run_id,
|
bot_run_id=s.bot_run_id,
|
||||||
bot_provider_id=s.bot_provider_id,
|
bot_provider_id=s.bot_provider_id,
|
||||||
|
bot_instance_id=s.bot_instance_id,
|
||||||
)
|
)
|
||||||
for s in self.sessions.values()
|
for s in self.sessions.values()
|
||||||
if s.name
|
if s.name
|
||||||
@ -343,7 +345,7 @@ class LobbyManager:
|
|||||||
removed_count = 0
|
removed_count = 0
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
lobbies_to_remove = []
|
lobbies_to_remove: list[Lobby] = []
|
||||||
for lobby in self.lobbies.values():
|
for lobby in self.lobbies.values():
|
||||||
if lobby.is_empty() and not lobby.private:
|
if lobby.is_empty() and not lobby.private:
|
||||||
lobbies_to_remove.append(lobby)
|
lobbies_to_remove.append(lobby)
|
||||||
|
@ -18,19 +18,33 @@ from pydantic import ValidationError
|
|||||||
# Import shared models
|
# Import shared models
|
||||||
try:
|
try:
|
||||||
# Try relative import first (when running as part of the package)
|
# Try relative import first (when running as part of the package)
|
||||||
from ...shared.models import SessionSaved, LobbySaved, SessionsPayload, NamePasswordRecord
|
from ...shared.models import (
|
||||||
|
SessionSaved,
|
||||||
|
LobbySaved,
|
||||||
|
SessionsPayload,
|
||||||
|
NamePasswordRecord,
|
||||||
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
# Try absolute import (when running directly)
|
# Try absolute import (when running directly)
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
||||||
from shared.models import SessionSaved, LobbySaved, SessionsPayload, NamePasswordRecord
|
sys.path.append(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
)
|
||||||
|
from shared.models import (
|
||||||
|
SessionSaved,
|
||||||
|
LobbySaved,
|
||||||
|
SessionsPayload,
|
||||||
|
NamePasswordRecord,
|
||||||
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
f"Failed to import shared models: {e}. Ensure shared/models.py is accessible and PYTHONPATH is correctly set."
|
f"Failed to import shared models: {e}. Ensure shared/models.py is accessible and PYTHONPATH is correctly set."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from core.lobby_manager import Lobby
|
||||||
from logger import logger
|
from logger import logger
|
||||||
|
|
||||||
# Import WebRTC signaling for peer management
|
# Import WebRTC signaling for peer management
|
||||||
@ -38,19 +52,41 @@ from websocket.webrtc_signaling import WebRTCSignalingHandlers
|
|||||||
|
|
||||||
# Use try/except for importing events to handle both relative and absolute imports
|
# Use try/except for importing events to handle both relative and absolute imports
|
||||||
try:
|
try:
|
||||||
from ..models.events import event_bus, SessionDisconnected, UserNameChanged, SessionJoinedLobby, SessionLeftLobby
|
from ..models.events import (
|
||||||
|
event_bus,
|
||||||
|
SessionDisconnected,
|
||||||
|
UserNameChanged,
|
||||||
|
SessionJoinedLobby,
|
||||||
|
SessionLeftLobby,
|
||||||
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
try:
|
||||||
from models.events import event_bus, SessionDisconnected, UserNameChanged, SessionJoinedLobby, SessionLeftLobby
|
from models.events import (
|
||||||
|
event_bus,
|
||||||
|
SessionDisconnected,
|
||||||
|
UserNameChanged,
|
||||||
|
SessionJoinedLobby,
|
||||||
|
SessionLeftLobby,
|
||||||
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Create dummy event system for standalone testing
|
# Create dummy event system for standalone testing
|
||||||
class DummyEventBus:
|
class DummyEventBus:
|
||||||
async def publish(self, event): pass
|
async def publish(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
event_bus = DummyEventBus()
|
event_bus = DummyEventBus()
|
||||||
class SessionDisconnected: pass
|
|
||||||
class UserNameChanged: pass
|
class SessionDisconnected:
|
||||||
class SessionJoinedLobby: pass
|
pass
|
||||||
class SessionLeftLobby: pass
|
|
||||||
|
class UserNameChanged:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SessionJoinedLobby:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SessionLeftLobby:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SessionConfig:
|
class SessionConfig:
|
||||||
@ -73,24 +109,30 @@ class SessionConfig:
|
|||||||
|
|
||||||
class Session:
|
class Session:
|
||||||
"""Individual session representing a user or bot connection"""
|
"""Individual session representing a user or bot connection"""
|
||||||
|
|
||||||
def __init__(self, id: str, is_bot: bool = False, has_media: bool = True):
|
def __init__(
|
||||||
|
self, id: str, bot_instance_id: Optional[str] = None, has_media: bool = True
|
||||||
|
):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Instantiating new session {id} (bot: {is_bot}, media: {has_media})"
|
f"Instantiating new session {id} (bot: {True if bot_instance_id else False}, media: {has_media})"
|
||||||
)
|
)
|
||||||
self.id = id
|
self.id = id
|
||||||
self.short = id[:8]
|
self.short = id[:8]
|
||||||
self.name = ""
|
self.name = ""
|
||||||
self.lobbies: List[Any] = [] # List of lobby objects this session is in
|
self.lobbies: List[Any] = [] # List of lobby objects this session is in
|
||||||
self.lobby_peers: Dict[str, List[str]] = {} # lobby ID -> list of peer session IDs
|
self.lobby_peers: Dict[
|
||||||
|
str, List[str]
|
||||||
|
] = {} # lobby ID -> list of peer session IDs
|
||||||
self.ws: Optional[WebSocket] = None
|
self.ws: Optional[WebSocket] = None
|
||||||
self.created_at = time.time()
|
self.created_at = time.time()
|
||||||
self.last_used = time.time()
|
self.last_used = time.time()
|
||||||
self.displaced_at: Optional[float] = None # When name was taken over
|
self.displaced_at: Optional[float] = None # When name was taken over
|
||||||
self.is_bot = is_bot # Whether this session represents a bot
|
|
||||||
self.has_media = has_media # Whether this session provides audio/video streams
|
self.has_media = has_media # Whether this session provides audio/video streams
|
||||||
self.bot_run_id: Optional[str] = None # Bot run ID for tracking
|
self.bot_run_id: Optional[str] = None # Bot run ID for tracking
|
||||||
self.bot_provider_id: Optional[str] = None # Bot provider ID
|
self.bot_provider_id: Optional[str] = None # Bot provider ID
|
||||||
|
self.bot_instance_id = (
|
||||||
|
bot_instance_id # Bot instance ID if this is a bot session
|
||||||
|
)
|
||||||
self.bot_instance_id: Optional[str] = None # Bot instance ID for tracking
|
self.bot_instance_id: Optional[str] = None # Bot instance ID for tracking
|
||||||
self.session_lock = threading.RLock() # Instance-level lock
|
self.session_lock = threading.RLock() # Instance-level lock
|
||||||
|
|
||||||
@ -103,17 +145,21 @@ class Session:
|
|||||||
old_name = self.name
|
old_name = self.name
|
||||||
self.name = name
|
self.name = name
|
||||||
self.update_last_used()
|
self.update_last_used()
|
||||||
|
|
||||||
# Get lobby IDs for event
|
# Get lobby IDs for event
|
||||||
lobby_ids = [lobby.id for lobby in self.lobbies]
|
lobby_ids = [lobby.id for lobby in self.lobbies]
|
||||||
|
|
||||||
# Publish name change event (don't await here to avoid blocking)
|
# Publish name change event (don't await here to avoid blocking)
|
||||||
asyncio.create_task(event_bus.publish(UserNameChanged(
|
asyncio.create_task(
|
||||||
session_id=self.id,
|
event_bus.publish(
|
||||||
old_name=old_name,
|
UserNameChanged(
|
||||||
new_name=name,
|
session_id=self.id,
|
||||||
lobby_ids=lobby_ids
|
old_name=old_name,
|
||||||
)))
|
new_name=name,
|
||||||
|
lobby_ids=lobby_ids,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def update_last_used(self):
|
def update_last_used(self):
|
||||||
"""Update the last_used timestamp"""
|
"""Update the last_used timestamp"""
|
||||||
@ -125,7 +171,7 @@ class Session:
|
|||||||
with self.session_lock:
|
with self.session_lock:
|
||||||
self.displaced_at = time.time()
|
self.displaced_at = time.time()
|
||||||
|
|
||||||
async def join_lobby(self, lobby):
|
async def join_lobby(self, lobby: Lobby):
|
||||||
"""Join a lobby and establish WebRTC peer connections"""
|
"""Join a lobby and establish WebRTC peer connections"""
|
||||||
with self.session_lock:
|
with self.session_lock:
|
||||||
if lobby not in self.lobbies:
|
if lobby not in self.lobbies:
|
||||||
@ -139,7 +185,7 @@ class Session:
|
|||||||
await lobby.addSession(self)
|
await lobby.addSession(self)
|
||||||
|
|
||||||
# Get existing peer sessions in this lobby for WebRTC setup
|
# Get existing peer sessions in this lobby for WebRTC setup
|
||||||
peer_sessions = []
|
peer_sessions: list[Session] = []
|
||||||
for session in lobby.sessions.values():
|
for session in lobby.sessions.values():
|
||||||
if (
|
if (
|
||||||
session.id != self.id and session.ws
|
session.id != self.id and session.ws
|
||||||
@ -151,16 +197,18 @@ class Session:
|
|||||||
await WebRTCSignalingHandlers.handle_add_peer(self, peer_session, lobby)
|
await WebRTCSignalingHandlers.handle_add_peer(self, peer_session, lobby)
|
||||||
|
|
||||||
# Publish join event
|
# Publish join event
|
||||||
await event_bus.publish(SessionJoinedLobby(
|
await event_bus.publish(
|
||||||
session_id=self.id,
|
SessionJoinedLobby(
|
||||||
lobby_id=lobby.id,
|
session_id=self.id,
|
||||||
session_name=self.name or self.short
|
lobby_id=lobby.id,
|
||||||
))
|
session_name=self.name or self.short,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def leave_lobby(self, lobby):
|
async def leave_lobby(self, lobby: Lobby):
|
||||||
"""Leave a lobby and clean up WebRTC peer connections"""
|
"""Leave a lobby and clean up WebRTC peer connections"""
|
||||||
# Get peer sessions before removing from lobby
|
# Get peer sessions before removing from lobby
|
||||||
peer_sessions = []
|
peer_sessions: list[Session] = []
|
||||||
if lobby.id in self.lobby_peers:
|
if lobby.id in self.lobby_peers:
|
||||||
for peer_id in self.lobby_peers[lobby.id]:
|
for peer_id in self.lobby_peers[lobby.id]:
|
||||||
peer_session = None
|
peer_session = None
|
||||||
@ -186,13 +234,15 @@ class Session:
|
|||||||
|
|
||||||
# Remove from lobby
|
# Remove from lobby
|
||||||
await lobby.removeSession(self)
|
await lobby.removeSession(self)
|
||||||
|
|
||||||
# Publish leave event
|
# Publish leave event
|
||||||
await event_bus.publish(SessionLeftLobby(
|
await event_bus.publish(
|
||||||
session_id=self.id,
|
SessionLeftLobby(
|
||||||
lobby_id=lobby.id,
|
session_id=self.id,
|
||||||
session_name=self.name or self.short
|
lobby_id=lobby.id,
|
||||||
))
|
session_name=self.name or self.short,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def model_dump(self) -> Dict[str, Any]:
|
def model_dump(self) -> Dict[str, Any]:
|
||||||
"""Convert session to dictionary format for API responses"""
|
"""Convert session to dictionary format for API responses"""
|
||||||
@ -200,25 +250,19 @@ class Session:
|
|||||||
data: Dict[str, Any] = {
|
data: Dict[str, Any] = {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"name": self.name or "",
|
"name": self.name or "",
|
||||||
"is_bot": self.is_bot,
|
"bot_instance_id": self.bot_instance_id,
|
||||||
"has_media": self.has_media,
|
"has_media": self.has_media,
|
||||||
"created_at": self.created_at,
|
"created_at": self.created_at,
|
||||||
"last_used": self.last_used,
|
"last_used": self.last_used,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Include bot_instance_id if this is a bot session and it has one
|
|
||||||
if self.is_bot and self.bot_instance_id:
|
|
||||||
data["bot_instance_id"] = self.bot_instance_id
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def to_saved(self) -> SessionSaved:
|
def to_saved(self) -> SessionSaved:
|
||||||
"""Convert session to saved format for persistence"""
|
"""Convert session to saved format for persistence"""
|
||||||
with self.session_lock:
|
with self.session_lock:
|
||||||
lobbies_list: List[LobbySaved] = [
|
lobbies_list: List[LobbySaved] = [
|
||||||
LobbySaved(
|
LobbySaved(id=lobby.id, name=lobby.name, private=lobby.private)
|
||||||
id=lobby.id, name=lobby.name, private=lobby.private
|
|
||||||
)
|
|
||||||
for lobby in self.lobbies
|
for lobby in self.lobbies
|
||||||
]
|
]
|
||||||
return SessionSaved(
|
return SessionSaved(
|
||||||
@ -228,7 +272,6 @@ class Session:
|
|||||||
created_at=self.created_at,
|
created_at=self.created_at,
|
||||||
last_used=self.last_used,
|
last_used=self.last_used,
|
||||||
displaced_at=self.displaced_at,
|
displaced_at=self.displaced_at,
|
||||||
is_bot=self.is_bot,
|
|
||||||
has_media=self.has_media,
|
has_media=self.has_media,
|
||||||
bot_run_id=self.bot_run_id,
|
bot_run_id=self.bot_run_id,
|
||||||
bot_provider_id=self.bot_provider_id,
|
bot_provider_id=self.bot_provider_id,
|
||||||
@ -238,20 +281,25 @@ class Session:
|
|||||||
|
|
||||||
class SessionManager:
|
class SessionManager:
|
||||||
"""Manages all sessions and their lifecycle"""
|
"""Manages all sessions and their lifecycle"""
|
||||||
|
|
||||||
def __init__(self, save_file: str = "sessions.json"):
|
def __init__(self, save_file: str = "sessions.json"):
|
||||||
self._instances: List[Session] = []
|
self._instances: List[Session] = []
|
||||||
self._save_file = save_file
|
self._save_file = save_file
|
||||||
self._loaded = False
|
self._loaded = False
|
||||||
self.lock = threading.RLock() # Thread safety for class-level operations
|
self.lock = threading.RLock() # Thread safety for class-level operations
|
||||||
|
|
||||||
# Background task management
|
# Background task management
|
||||||
self.cleanup_task_running = False
|
self.cleanup_task_running = False
|
||||||
self.cleanup_task: Optional[asyncio.Task] = None
|
self.cleanup_task: Optional[asyncio.Task] = None
|
||||||
self.validation_task_running = False
|
self.validation_task_running = False
|
||||||
self.validation_task: Optional[asyncio.Task] = None
|
self.validation_task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
def create_session(self, session_id: Optional[str] = None, is_bot: bool = False, has_media: bool = True) -> Session:
|
def create_session(
|
||||||
|
self,
|
||||||
|
session_id: Optional[str] = None,
|
||||||
|
bot_instance_id: Optional[str] = None,
|
||||||
|
has_media: bool = True,
|
||||||
|
) -> Session:
|
||||||
"""Create a new session with given or generated ID"""
|
"""Create a new session with given or generated ID"""
|
||||||
if not session_id:
|
if not session_id:
|
||||||
session_id = secrets.token_hex(16)
|
session_id = secrets.token_hex(16)
|
||||||
@ -266,20 +314,29 @@ class SessionManager:
|
|||||||
return existing_session
|
return existing_session
|
||||||
|
|
||||||
# Create new session
|
# Create new session
|
||||||
session = Session(session_id, is_bot=is_bot, has_media=has_media)
|
session = Session(
|
||||||
|
session_id, bot_instance_id=bot_instance_id, has_media=has_media
|
||||||
|
)
|
||||||
self._instances.append(session)
|
self._instances.append(session)
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
return session
|
return session
|
||||||
|
|
||||||
def get_or_create_session(self, session_id: Optional[str] = None, is_bot: bool = False, has_media: bool = True) -> Session:
|
def get_or_create_session(
|
||||||
|
self,
|
||||||
|
session_id: Optional[str] = None,
|
||||||
|
bot_instance_id: Optional[str] = None,
|
||||||
|
has_media: bool = True,
|
||||||
|
) -> Session:
|
||||||
"""Get existing session or create a new one"""
|
"""Get existing session or create a new one"""
|
||||||
if session_id:
|
if session_id:
|
||||||
existing_session = self.get_session(session_id)
|
existing_session = self.get_session(session_id)
|
||||||
if existing_session:
|
if existing_session:
|
||||||
return existing_session
|
return existing_session
|
||||||
|
|
||||||
return self.create_session(session_id, is_bot=is_bot, has_media=has_media)
|
return self.create_session(
|
||||||
|
session_id, bot_instance_id=bot_instance_id, has_media=has_media
|
||||||
|
)
|
||||||
|
|
||||||
def get_session(self, session_id: str) -> Optional[Session]:
|
def get_session(self, session_id: str) -> Optional[Session]:
|
||||||
"""Get session by ID"""
|
"""Get session by ID"""
|
||||||
@ -321,14 +378,18 @@ class SessionManager:
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
if session in self._instances:
|
if session in self._instances:
|
||||||
self._instances.remove(session)
|
self._instances.remove(session)
|
||||||
|
|
||||||
# Publish disconnect event
|
# Publish disconnect event
|
||||||
lobby_ids = [lobby.id for lobby in session.lobbies]
|
lobby_ids = [lobby.id for lobby in session.lobbies]
|
||||||
asyncio.create_task(event_bus.publish(SessionDisconnected(
|
asyncio.create_task(
|
||||||
session_id=session.id,
|
event_bus.publish(
|
||||||
session_name=session.name or session.short,
|
SessionDisconnected(
|
||||||
lobby_ids=lobby_ids
|
session_id=session.id,
|
||||||
)))
|
session_name=session.name or session.short,
|
||||||
|
lobby_ids=lobby_ids,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save all sessions to disk"""
|
"""Save all sessions to disk"""
|
||||||
@ -355,9 +416,7 @@ class SessionManager:
|
|||||||
# Atomic rename
|
# Atomic rename
|
||||||
os.rename(temp_file, self._save_file)
|
os.rename(temp_file, self._save_file)
|
||||||
|
|
||||||
logger.info(
|
logger.info(f"Saved {len(sessions_list)} sessions to {self._save_file}")
|
||||||
f"Saved {len(sessions_list)} sessions to {self._save_file}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to save sessions: {e}")
|
logger.error(f"Failed to save sessions: {e}")
|
||||||
# Clean up temp file if it exists
|
# Clean up temp file if it exists
|
||||||
@ -423,20 +482,18 @@ class SessionManager:
|
|||||||
|
|
||||||
session = Session(
|
session = Session(
|
||||||
s_saved.id,
|
s_saved.id,
|
||||||
is_bot=getattr(s_saved, "is_bot", False),
|
bot_instance_id=s_saved.bot_instance_id,
|
||||||
has_media=getattr(s_saved, "has_media", True),
|
has_media=s_saved.has_media,
|
||||||
)
|
)
|
||||||
session.name = name
|
session.name = name
|
||||||
session.created_at = created_at
|
session.created_at = created_at
|
||||||
session.last_used = last_used
|
session.last_used = last_used
|
||||||
session.displaced_at = displaced_at
|
session.displaced_at = displaced_at
|
||||||
session.is_bot = getattr(s_saved, "is_bot", False)
|
session.bot_run_id = s_saved.bot_run_id
|
||||||
session.has_media = getattr(s_saved, "has_media", True)
|
session.bot_provider_id = s_saved.bot_provider_id
|
||||||
session.bot_run_id = getattr(s_saved, "bot_run_id", None)
|
|
||||||
session.bot_provider_id = getattr(s_saved, "bot_provider_id", None)
|
|
||||||
|
|
||||||
# Note: Lobby restoration will be handled by LobbyManager
|
# Note: Lobby restoration will be handled by LobbyManager
|
||||||
|
|
||||||
self._instances.append(session)
|
self._instances.append(session)
|
||||||
sessions_loaded += 1
|
sessions_loaded += 1
|
||||||
|
|
||||||
@ -480,10 +537,10 @@ class SessionManager:
|
|||||||
"""Clean up old/stale sessions and return count of removed sessions"""
|
"""Clean up old/stale sessions and return count of removed sessions"""
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
removed_count = 0
|
removed_count = 0
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
sessions_to_remove = []
|
sessions_to_remove: list[Session] = []
|
||||||
|
|
||||||
for session in self._instances:
|
for session in self._instances:
|
||||||
with session.session_lock:
|
with session.session_lock:
|
||||||
if self._should_remove_session_static(
|
if self._should_remove_session_static(
|
||||||
@ -495,8 +552,11 @@ class SessionManager:
|
|||||||
current_time,
|
current_time,
|
||||||
):
|
):
|
||||||
sessions_to_remove.append(session)
|
sessions_to_remove.append(session)
|
||||||
|
|
||||||
if len(sessions_to_remove) >= SessionConfig.MAX_SESSIONS_PER_CLEANUP:
|
if (
|
||||||
|
len(sessions_to_remove)
|
||||||
|
>= SessionConfig.MAX_SESSIONS_PER_CLEANUP
|
||||||
|
):
|
||||||
break
|
break
|
||||||
|
|
||||||
# Remove sessions
|
# Remove sessions
|
||||||
@ -505,22 +565,24 @@ class SessionManager:
|
|||||||
# Clean up websocket if open
|
# Clean up websocket if open
|
||||||
if session.ws:
|
if session.ws:
|
||||||
asyncio.create_task(session.ws.close())
|
asyncio.create_task(session.ws.close())
|
||||||
|
|
||||||
# Remove from lobbies (will be handled by lobby manager events)
|
# Remove from lobbies (will be handled by lobby manager events)
|
||||||
for lobby in session.lobbies[:]:
|
for lobby in session.lobbies[:]:
|
||||||
asyncio.create_task(session.leave_lobby(lobby))
|
asyncio.create_task(session.leave_lobby(lobby))
|
||||||
|
|
||||||
self._instances.remove(session)
|
self._instances.remove(session)
|
||||||
removed_count += 1
|
removed_count += 1
|
||||||
|
|
||||||
logger.info(f"Cleaned up session {session.getName()}")
|
logger.info(f"Cleaned up session {session.getName()}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error cleaning up session {session.getName()}: {e}")
|
logger.warning(
|
||||||
|
f"Error cleaning up session {session.getName()}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
if removed_count > 0:
|
if removed_count > 0:
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
return removed_count
|
return removed_count
|
||||||
|
|
||||||
async def start_background_tasks(self):
|
async def start_background_tasks(self):
|
||||||
@ -560,7 +622,9 @@ class SessionManager:
|
|||||||
try:
|
try:
|
||||||
removed_count = self.cleanup_old_sessions()
|
removed_count = self.cleanup_old_sessions()
|
||||||
if removed_count > 0:
|
if removed_count > 0:
|
||||||
logger.info(f"Periodic cleanup removed {removed_count} old sessions")
|
logger.info(
|
||||||
|
f"Periodic cleanup removed {removed_count} old sessions"
|
||||||
|
)
|
||||||
cleanup_errors = 0 # Reset error counter on success
|
cleanup_errors = 0 # Reset error counter on success
|
||||||
|
|
||||||
# Run cleanup at configured interval
|
# Run cleanup at configured interval
|
||||||
@ -586,7 +650,9 @@ class SessionManager:
|
|||||||
try:
|
try:
|
||||||
issues = self.validate_session_integrity()
|
issues = self.validate_session_integrity()
|
||||||
if issues:
|
if issues:
|
||||||
logger.warning(f"Session integrity issues found: {len(issues)} issues")
|
logger.warning(
|
||||||
|
f"Session integrity issues found: {len(issues)} issues"
|
||||||
|
)
|
||||||
for issue in issues[:10]: # Log first 10 issues
|
for issue in issues[:10]: # Log first 10 issues
|
||||||
logger.warning(f"Integrity issue: {issue}")
|
logger.warning(f"Integrity issue: {issue}")
|
||||||
|
|
||||||
@ -598,27 +664,36 @@ class SessionManager:
|
|||||||
def validate_session_integrity(self) -> List[str]:
|
def validate_session_integrity(self) -> List[str]:
|
||||||
"""Validate session integrity and return list of issues"""
|
"""Validate session integrity and return list of issues"""
|
||||||
issues = []
|
issues = []
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
for session in self._instances:
|
for session in self._instances:
|
||||||
with session.session_lock:
|
with session.session_lock:
|
||||||
# Check for sessions with invalid state
|
# Check for sessions with invalid state
|
||||||
if not session.id:
|
if not session.id:
|
||||||
issues.append(f"Session with empty ID: {session}")
|
issues.append(f"Session with empty ID: {session}")
|
||||||
|
|
||||||
if session.created_at > time.time():
|
if session.created_at > time.time():
|
||||||
issues.append(f"Session {session.getName()} has future creation time")
|
issues.append(
|
||||||
|
f"Session {session.getName()} has future creation time"
|
||||||
|
)
|
||||||
|
|
||||||
if session.last_used > time.time():
|
if session.last_used > time.time():
|
||||||
issues.append(f"Session {session.getName()} has future last_used time")
|
issues.append(
|
||||||
|
f"Session {session.getName()} has future last_used time"
|
||||||
|
)
|
||||||
|
|
||||||
# Check for duplicate names
|
# Check for duplicate names
|
||||||
if session.name:
|
if session.name:
|
||||||
count = sum(1 for s in self._instances
|
count = sum(
|
||||||
if s.name and s.name.lower() == session.name.lower())
|
1
|
||||||
|
for s in self._instances
|
||||||
|
if s.name and s.name.lower() == session.name.lower()
|
||||||
|
)
|
||||||
if count > 1:
|
if count > 1:
|
||||||
issues.append(f"Duplicate name '{session.name}' found in {count} sessions")
|
issues.append(
|
||||||
|
f"Duplicate name '{session.name}' found in {count} sessions"
|
||||||
|
)
|
||||||
|
|
||||||
return issues
|
return issues
|
||||||
|
|
||||||
async def _cleanup_all_sessions(self):
|
async def _cleanup_all_sessions(self):
|
||||||
@ -629,8 +704,10 @@ class SessionManager:
|
|||||||
if session.ws:
|
if session.ws:
|
||||||
await session.ws.close()
|
await session.ws.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error closing WebSocket for {session.getName()}: {e}")
|
logger.warning(
|
||||||
|
f"Error closing WebSocket for {session.getName()}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("All sessions cleaned up")
|
logger.info("All sessions cleaned up")
|
||||||
|
|
||||||
def get_all_sessions(self) -> List[Session]:
|
def get_all_sessions(self) -> List[Session]:
|
||||||
|
@ -43,10 +43,10 @@ class ParticipantModel(BaseModel):
|
|||||||
session_id: str
|
session_id: str
|
||||||
live: bool
|
live: bool
|
||||||
protected: bool
|
protected: bool
|
||||||
is_bot: bool = False
|
|
||||||
has_media: bool = True # Whether this participant provides audio/video streams
|
has_media: bool = True # Whether this participant provides audio/video streams
|
||||||
bot_run_id: Optional[str] = None
|
bot_run_id: Optional[str] = None
|
||||||
bot_provider_id: Optional[str] = None
|
bot_provider_id: Optional[str] = None
|
||||||
|
bot_instance_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -138,6 +138,11 @@ class SessionResponse(BaseModel):
|
|||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
lobbies: List[LobbyModel]
|
lobbies: List[LobbyModel]
|
||||||
|
protected: bool = False
|
||||||
|
has_media: bool = False
|
||||||
|
bot_run_id: Optional[str] = None
|
||||||
|
bot_provider_id: Optional[str] = None
|
||||||
|
bot_instance_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class LobbyCreateData(BaseModel):
|
class LobbyCreateData(BaseModel):
|
||||||
@ -317,7 +322,6 @@ class SessionSaved(BaseModel):
|
|||||||
created_at: float = 0.0
|
created_at: float = 0.0
|
||||||
last_used: float = 0.0
|
last_used: float = 0.0
|
||||||
displaced_at: Optional[float] = None # When name was taken over
|
displaced_at: Optional[float] = None # When name was taken over
|
||||||
is_bot: bool = False # Whether this session represents a bot
|
|
||||||
has_media: bool = True # Whether this session provides audio/video streams
|
has_media: bool = True # Whether this session provides audio/video streams
|
||||||
bot_run_id: Optional[str] = None # Bot run ID for tracking
|
bot_run_id: Optional[str] = None # Bot run ID for tracking
|
||||||
bot_provider_id: Optional[str] = None # Bot provider ID
|
bot_provider_id: Optional[str] = None # Bot provider ID
|
||||||
|
Loading…
x
Reference in New Issue
Block a user