diff --git a/.dockerignore b/.dockerignore index 2633f3b..6e042c0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,24 +3,13 @@ !server !client !shared -node_modules -build -dist +**/node_modules +**/build +**/dist **/__pycache__ **/.venv -*.pyc -*.pyo -*.pyd -*.log -*.swp -*.swo -.env -.env.* -*.pem -*.key -*.bak -*.tmp -*.local -package-lock.json -yarn.lock -pnpm-lock.yaml +**/.env +**/*.pem +**/*.key +**/package-lock.json +**/*.pyc \ No newline at end of file diff --git a/Dockerfile.voicebot b/Dockerfile.voicebot index f8cfb56..2dfb6b4 100644 --- a/Dockerfile.voicebot +++ b/Dockerfile.voicebot @@ -1,4 +1,4 @@ -FROM ubuntu:oracular +FROM ubuntu:noble # Stick with Python3.12 (plucky has 3.13) # Install some utilities frequently used diff --git a/clean-venvs b/clean-venvs deleted file mode 100755 index 2b1a3ac..0000000 --- a/clean-venvs +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -find . -name .venv | while read path; do - echo "Removing ${path}" - sudo rm -rf "${path}" -done -find . -name .python-version | while read path; do - echo "Removing ${path}" - sudo rm "${path}" -done diff --git a/client/README.md b/client/README.md index 16fe669..6bc7bc3 100644 --- a/client/README.md +++ b/client/README.md @@ -2,6 +2,6 @@ To deploy: ```bash export PUBLIC_URL=/ai-voicebot -npm run build -rsync --delete -avrpl build/ webserver:/var/www/ketrenos.com/ai-voicebot/ +docker compose exec client npm run build +rsync --delete -avrpl client/build/ webserver:/var/www/ketrenos.com/ai-voicebot/ ``` diff --git a/client/entrypoint.sh b/client/entrypoint.sh old mode 100644 new mode 100755 diff --git a/client/openapi-schema.json b/client/openapi-schema.json index e8a48e0..61b2800 100644 --- a/client/openapi-schema.json +++ b/client/openapi-schema.json @@ -727,14 +727,14 @@ } } }, - "/ai-voicebot/api/bots/config/lobby/{lobby_id}/bot/{bot_name}": { + "/ai-voicebot/api/bots/config/lobby/{lobby_id}/bot/{bot_instance_id}": { "get": { "tags": [ "Bot Configuration" ], "summary": "Get Lobby Bot Config", "description": "Get specific bot configuration for a lobby", - "operationId": "get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__get", + "operationId": "get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_instance_id__get", "parameters": [ { "name": "lobby_id", @@ -746,12 +746,12 @@ } }, { - "name": "bot_name", + "name": "bot_instance_id", "in": "path", "required": true, "schema": { "type": "string", - "title": "Bot Name" + "title": "Bot Instance Id" } } ], @@ -784,7 +784,7 @@ ], "summary": "Delete Bot Config", "description": "Delete bot configuration for a lobby", - "operationId": "delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__delete", + "operationId": "delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_instance_id__delete", "parameters": [ { "name": "lobby_id", @@ -796,12 +796,12 @@ } }, { - "name": "bot_name", + "name": "bot_instance_id", "in": "path", "required": true, "schema": { "type": "string", - "title": "Bot Name" + "title": "Bot Instance Id" } } ], @@ -813,7 +813,7 @@ "schema": { "type": "object", "additionalProperties": true, - "title": "Response Delete Bot Config Ai Voicebot Api Bots Config Lobby Lobby Id Bot Bot Name Delete" + "title": "Response Delete Bot Config Ai Voicebot Api Bots Config Lobby Lobby Id Bot Bot Instance Id Delete" } } } @@ -1644,9 +1644,9 @@ }, "BotConfigUpdateRequest": { "properties": { - "bot_name": { + "bot_instance_id": { "type": "string", - "title": "Bot Name" + "title": "Bot Instance Id" }, "lobby_id": { "type": "string", @@ -1660,7 +1660,7 @@ }, "type": "object", "required": [ - "bot_name", + "bot_instance_id", "lobby_id", "config_values" ], diff --git a/client/src/App.tsx b/client/src/App.tsx index 2820b66..b753b36 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -213,6 +213,9 @@ const LobbyView: React.FC = (props: LobbyProps) => { AI Voice Chat Lobby: {lobbyName} + + {lobby ? `Lobby ID: ${lobby.id}` : creatingLobby ? "Joining lobby..." : "Not in a lobby"} + {session.name && You are logged in as: {session.name}} diff --git a/client/src/BotConfig.tsx b/client/src/BotConfig.tsx index 4be33ca..2d3c5e8 100644 --- a/client/src/BotConfig.tsx +++ b/client/src/BotConfig.tsx @@ -6,51 +6,21 @@ * form controls for each configuration parameter. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from "react"; import { base } from "./Common"; import "./BotConfig.css"; - -interface ConfigParameter { - name: string; - type: "string" | "number" | "boolean" | "select" | "range"; - label: string; - description: string; - default_value?: any; - required?: boolean; - options?: Array<{ value: string; label: string }>; - min_value?: number; - max_value?: number; - step?: number; - max_length?: number; - pattern?: string; -} - -interface ConfigSchema { - bot_name: string; - version: string; - parameters: ConfigParameter[]; - categories?: Array<{ name: string; parameters: string[] }>; -} - -interface BotConfig { - bot_name: string; - lobby_id: string; - provider_id: string; - config_values: { [key: string]: any }; - created_at: number; - updated_at: number; - created_by: string; -} +import type { components } from "./api-types"; interface BotConfigProps { + botInstanceId?: string; botName: string; lobbyId: string; - onConfigUpdate?: (config: BotConfig) => void; + onConfigUpdate?: (config: components["schemas"]["BotLobbyConfig"]) => void; } -const BotConfigComponent: React.FC = ({ botName, lobbyId, onConfigUpdate }) => { - const [schema, setSchema] = useState(null); - const [currentConfig, setCurrentConfig] = useState(null); +const BotConfigComponent: React.FC = ({ botInstanceId, botName, lobbyId, onConfigUpdate }) => { + const [schema, setSchema] = useState(null); + const [currentConfig, setCurrentConfig] = useState(null); const [configValues, setConfigValues] = useState<{ [key: string]: any }>({}); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -58,50 +28,52 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf const [refreshing, setRefreshing] = useState(false); // Fetch configuration schema - const fetchSchema = async (forceRefresh = false) => { - try { - setLoading(true); - setError(null); + const fetchSchema = useCallback( + async (forceRefresh = false) => { + try { + setLoading(true); + setError(null); - // Use refresh endpoint if force refresh is requested - const url = forceRefresh - ? `${base}/api/bots/config/schema/${botName}/refresh` - : `${base}/api/bots/config/schema/${botName}`; + // Use refresh endpoint if force refresh is requested + const url = forceRefresh + ? `${base}/api/bots/config/schema/${botName}/refresh` + : `${base}/api/bots/config/schema/${botName}`; - const method = forceRefresh ? "POST" : "GET"; + const method = forceRefresh ? "POST" : "GET"; - const response = await fetch(url, { method }); + const response = await fetch(url, { method }); - if (response.ok) { - const responseData = await response.json(); - // For refresh endpoint, the schema is in the 'schema' field - const schemaData = forceRefresh ? responseData.schema : responseData; + if (response.ok) { + const responseData = await response.json(); + // For refresh endpoint, the schema is in the 'schema' field + const schemaData = forceRefresh ? responseData.schema : responseData; + setSchema(schemaData); - setSchema(schemaData); - - // Initialize config values with defaults - const defaultValues: { [key: string]: any } = {}; - schemaData.parameters.forEach((param: ConfigParameter) => { - if (param.default_value !== undefined) { - defaultValues[param.name] = param.default_value; - } - }); - setConfigValues(defaultValues); - } else if (response.status === 404) { - setError(`Bot "${botName}" does not support configuration`); - } else { - setError("Failed to fetch configuration schema"); + // Initialize config values with defaults + const defaultValues: { [key: string]: any } = {}; + schemaData.parameters.forEach((param: components["schemas"]["BotConfigParameter"]) => { + if (param.default_value !== undefined) { + defaultValues[param.name] = param.default_value; + } + }); + setConfigValues(defaultValues); + } else if (response.status === 404) { + setError(`Bot "${botName}" does not support configuration`); + } else { + setError("Failed to fetch configuration schema"); + } + } catch (err) { + setError("Network error while fetching configuration schema"); + } finally { + setLoading(false); } - } catch (err) { - setError("Network error while fetching configuration schema"); - } finally { - setLoading(false); - } - }; + }, + [botName] + ); useEffect(() => { fetchSchema(); - }, [botName]); + }, [botName, fetchSchema]); // Handle schema refresh const handleRefreshSchema = async () => { @@ -114,12 +86,12 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf useEffect(() => { const fetchCurrentConfig = async () => { try { - const response = await fetch(`${base}/api/bots/config/lobby/${lobbyId}/bot/${botName}`); + const response = await fetch(`${base}/api/bots/config/lobby/${lobbyId}/bot/${botInstanceId}`); if (response.ok) { const config = await response.json(); setCurrentConfig(config); - setConfigValues({ ...configValues, ...config.config_values }); + setConfigValues((prev) => ({ ...prev, ...config.config_values })); } // If 404, no existing config - that's fine } catch (err) { @@ -127,10 +99,15 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf } }; - if (schema) { + if (schema && botInstanceId) { fetchCurrentConfig(); } - }, [botName, lobbyId, schema]); + }, [botName, lobbyId, schema, botInstanceId]); + + // Check if bot instance ID is available + if (!botInstanceId) { + return
Configuration not available - no bot instance selected
; + } const handleValueChange = (paramName: string, value: any) => { setConfigValues((prev) => ({ @@ -150,7 +127,7 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf "Content-Type": "application/json", }, body: JSON.stringify({ - bot_name: botName, + bot_instance_id: botInstanceId, lobby_id: lobbyId, config_values: configValues, }), @@ -173,7 +150,7 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf } }; - const renderParameter = (param: ConfigParameter) => { + const renderParameter = (param: components["schemas"]["BotConfigParameter"]) => { const value = configValues[param.name]; switch (param.type) { @@ -201,11 +178,18 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf onChange={(e) => handleValueChange(param.name, e.target.value)} className="config-select" > - {param.options?.map((option) => ( - - ))} + {param.options?.map((option, index) => { + // Handle both formats: {value: string, label: string} and generic {[key: string]: string} + const optionValue = + typeof option === "object" && "value" in option ? option.value : Object.keys(option)[0]; + const optionLabel = + typeof option === "object" && "label" in option ? option.label : Object.values(option)[0]; + return ( + + ); + })}

{param.description}

@@ -237,9 +221,9 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf handleValueChange(param.name, Number(e.target.value))} className="config-input" @@ -255,8 +239,8 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf handleValueChange(param.name, e.target.value)} className="config-input" @@ -271,17 +255,22 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf if (!schema) return null; if (schema.categories && schema.categories.length > 0) { - return schema.categories.map((category) => ( -
-

{category.name}

-
- {category.parameters?.map((paramName) => { - const param = schema.parameters?.find((p) => p.name === paramName); - return param ?
{renderParameter(param)}
: null; - }) || null} + return schema.categories.map((category, categoryIndex) => { + // Handle API format: each category is an object with string keys mapping to string arrays + const categoryName = Object.keys(category)[0]; + const parameterNames = category[categoryName]; + return ( +
+

{categoryName}

+
+ {parameterNames?.map((paramName: string) => { + const param = schema.parameters?.find((p) => p.name === paramName); + return param ?
{renderParameter(param)}
: null; + }) || null} +
-
- )); + ); + }); } else { return schema.parameters?.map((param) =>
{renderParameter(param)}
) || null; } @@ -303,7 +292,7 @@ const BotConfigComponent: React.FC = ({ botName, lobbyId, onConf
-

Configure {botName}

+

Configure Bot {botInstanceId}

-
{renderParametersByCategory()}
+
+ {renderParametersByCategory()} +