Bot config no longer gives errors on frontend

This commit is contained in:
James Ketr 2025-09-15 12:25:29 -07:00
parent bef73a8af4
commit 716c508f45
25 changed files with 3327 additions and 569 deletions

View File

@ -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

View File

@ -1,4 +1,4 @@
FROM ubuntu:oracular
FROM ubuntu:noble
# Stick with Python3.12 (plucky has 3.13)
# Install some utilities frequently used

View File

@ -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

View File

@ -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/
```

0
client/entrypoint.sh Normal file → Executable file
View File

View File

@ -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"
],

View File

@ -213,6 +213,9 @@ const LobbyView: React.FC<LobbyProps> = (props: LobbyProps) => {
<Box sx={{ mb: 2, display: "flex", gap: 2, alignItems: "flex-start", flexDirection: "column" }}>
<Box>
<Typography variant="h5">AI Voice Chat Lobby: {lobbyName}</Typography>
<Typography variant="caption">
{lobby ? `Lobby ID: ${lobby.id}` : creatingLobby ? "Joining lobby..." : "Not in a lobby"}
</Typography>
</Box>
<Box>
{session.name && <Typography>You are logged in as: {session.name}</Typography>}

View File

@ -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<BotConfigProps> = ({ botName, lobbyId, onConfigUpdate }) => {
const [schema, setSchema] = useState<ConfigSchema | null>(null);
const [currentConfig, setCurrentConfig] = useState<BotConfig | null>(null);
const BotConfigComponent: React.FC<BotConfigProps> = ({ botInstanceId, botName, lobbyId, onConfigUpdate }) => {
const [schema, setSchema] = useState<components["schemas"]["BotConfigSchema"] | null>(null);
const [currentConfig, setCurrentConfig] = useState<components["schemas"]["BotLobbyConfig"] | null>(null);
const [configValues, setConfigValues] = useState<{ [key: string]: any }>({});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@ -58,50 +28,52 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ 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<BotConfigProps> = ({ 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<BotConfigProps> = ({ 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 <div className="bot-config-unavailable">Configuration not available - no bot instance selected</div>;
}
const handleValueChange = (paramName: string, value: any) => {
setConfigValues((prev) => ({
@ -150,7 +127,7 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ 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<BotConfigProps> = ({ 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<BotConfigProps> = ({ botName, lobbyId, onConf
onChange={(e) => handleValueChange(param.name, e.target.value)}
className="config-select"
>
{param.options?.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</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 (
<option key={index} value={optionValue}>
{optionLabel}
</option>
);
})}
</select>
<p className="config-description">{param.description}</p>
</div>
@ -237,9 +221,9 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
<label className="config-label">{param.label}</label>
<input
type="number"
min={param.min_value}
max={param.max_value}
step={param.step || 1}
min={param.min_value ?? undefined}
max={param.max_value ?? undefined}
step={param.step ?? 1}
value={value || ""}
onChange={(e) => handleValueChange(param.name, Number(e.target.value))}
className="config-input"
@ -255,8 +239,8 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
<label className="config-label">{param.label}</label>
<input
type="text"
maxLength={param.max_length}
pattern={param.pattern}
maxLength={param.max_length ?? undefined}
pattern={param.pattern ?? undefined}
value={value || ""}
onChange={(e) => handleValueChange(param.name, e.target.value)}
className="config-input"
@ -271,17 +255,22 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
if (!schema) return null;
if (schema.categories && schema.categories.length > 0) {
return schema.categories.map((category) => (
<div key={category.name} className="config-category">
<h4 className="category-title">{category.name}</h4>
<div className="category-parameters">
{category.parameters?.map((paramName) => {
const param = schema.parameters?.find((p) => p.name === paramName);
return param ? <div key={paramName}>{renderParameter(param)}</div> : 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 (
<div key={categoryIndex} className="config-category">
<h4 className="category-title">{categoryName}</h4>
<div className="category-parameters">
{parameterNames?.map((paramName: string) => {
const param = schema.parameters?.find((p) => p.name === paramName);
return param ? <div key={paramName}>{renderParameter(param)}</div> : null;
}) || null}
</div>
</div>
</div>
));
);
});
} else {
return schema.parameters?.map((param) => <div key={param.name}>{renderParameter(param)}</div>) || null;
}
@ -303,7 +292,7 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
<div className="bot-config-container">
<div className="bot-config-header">
<div className="bot-config-title">
<h3>Configure {botName}</h3>
<h3>Configure Bot {botInstanceId}</h3>
<button
onClick={handleRefreshSchema}
disabled={refreshing || loading}
@ -319,7 +308,9 @@ const BotConfigComponent: React.FC<BotConfigProps> = ({ botName, lobbyId, onConf
)}
</div>
<div className="bot-config-form">{renderParametersByCategory()}</div>
<div className="bot-config-form">
<React.Fragment key="parameters-container">{renderParametersByCategory()}</React.Fragment>
</div>
<div className="bot-config-actions">
<button onClick={handleSave} disabled={saving} className="config-save-button">

View File

@ -29,7 +29,6 @@ import {
} from "@mui/icons-material";
import { botsApi, BotInfoModel, BotProviderPublicModel, BotJoinLobbyRequest } from "./api-client";
import BotConfig from "./BotConfig";
import BotConfigComponent from "./BotConfig";
interface BotManagerProps {
lobbyId: string;
@ -239,7 +238,7 @@ const BotManager: React.FC<BotManagerProps> = ({ lobbyId, onBotAdded, sx }) => {
}
secondaryTypographyProps={{ component: "div" }}
/>
{isBotConfigurable(botInfo) && (
{false && isBotConfigurable(botInfo) && (
<IconButton
onClick={() => handleOpenConfigDialog(botInfo.name)}
size="small"

View File

@ -237,6 +237,7 @@ const UserList: React.FC<UserListProps> = (props: UserListProps) => {
<DialogContent>
{selectedBotForConfig && (
<BotConfig
botInstanceId={selectedBotForConfig.bot_instance_id || ""}
botName={selectedBotForConfig.name?.replace(/-bot$/, "") || "unknown"}
lobbyId={lobbyId}
onConfigUpdate={(config) => {

View File

@ -182,7 +182,10 @@ export class ApiClient {
}
async createRegisterBotProvider(data: any): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/providers/register`), { method: "POST", body: data });
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/providers/register`), {
method: "POST",
body: data,
});
}
async getListBotProviders(): Promise<any> {
@ -198,7 +201,9 @@ export class ApiClient {
}
async createRequestBotLeaveLobby(bot_instance_id: string): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/instances/${bot_instance_id}/leave`), { method: "POST" });
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/instances/${bot_instance_id}/leave`), {
method: "POST",
});
}
async getBotInstance(bot_instance_id: string): Promise<any> {
@ -217,16 +222,24 @@ export class ApiClient {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}`), { method: "DELETE" });
}
async getLobbyBotConfig(lobby_id: string, bot_name: string): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}/bot/${bot_name}`), { method: "GET" });
async getLobbyBotConfig(lobby_id: string, bot_instance_id: string): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}/bot/${bot_instance_id}`), {
method: "GET",
});
}
async deleteBotConfig(lobby_id: string, bot_name: string): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}/bot/${bot_name}`), { method: "DELETE" });
async deleteBotConfig(lobby_id: string, bot_instance_id: string): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/lobby/${lobby_id}/bot/${bot_instance_id}`), {
method: "DELETE",
});
}
async createUpdateBotConfig(data: any, params?: Record<string, string>): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/update`), { method: "POST", body: data, params });
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/update`), {
method: "POST",
body: data,
params,
});
}
async getConfigStatistics(): Promise<any> {
@ -238,11 +251,15 @@ export class ApiClient {
}
async createRefreshBotSchema(bot_name: string): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/schema/${bot_name}/refresh`), { method: "POST" });
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/schema/${bot_name}/refresh`), {
method: "POST",
});
}
async deleteClearBotSchemaCache(bot_name: string): Promise<any> {
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/schema/${bot_name}/cache`), { method: "DELETE" });
return this.request<any>(this.getApiPath(`/ai-voicebot/api/bots/config/schema/${bot_name}/cache`), {
method: "DELETE",
});
}
async getReadinessProbe(): Promise<any> {
@ -323,13 +340,13 @@ export const botsApi = {
getSchema: (bot_name: string) => apiClient.getBotConfigSchema(bot_name),
getBotConfigs: (lobby_id: string) => apiClient.getLobbyBotConfigs(lobby_id),
deleteBotConfigs: (lobby_id: string) => apiClient.deleteLobbyConfigs(lobby_id),
getBotConfig: (lobby_id: string, bot_name: string) => apiClient.getLobbyBotConfig(lobby_id, bot_name),
deleteBotConfig: (lobby_id: string, bot_name: string) => apiClient.deleteBotConfig(lobby_id, bot_name),
getBotConfig: (lobby_id: string, bot_instance_id: string) => apiClient.getLobbyBotConfig(lobby_id, bot_instance_id),
deleteBotConfig: (lobby_id: string, bot_instance_id: string) => apiClient.deleteBotConfig(lobby_id, bot_instance_id),
updateConfig: (data: any) => apiClient.createUpdateBotConfig(data),
getConfigStatistics: () => apiClient.getConfigStatistics(),
refreshAllSchemas: () => apiClient.createRefreshBotSchemas(),
refreshSchema: (bot_name: string) => apiClient.createRefreshBotSchema(bot_name),
clearSchemaCache: (bot_name: string) => apiClient.deleteClearBotSchemaCache(bot_name)
clearSchemaCache: (bot_name: string) => apiClient.deleteClearBotSchemaCache(bot_name),
};
export const metricsApi = {

View File

@ -5,14 +5,14 @@ import { base } from './Common';
export const knownEndpoints = new Set([
`DELETE:${base}/api/bots/config/lobby/{lobby_id}`,
`DELETE:${base}/api/bots/config/lobby/{lobby_id}/bot/{bot_name}`,
`DELETE:${base}/api/bots/config/lobby/{lobby_id}/bot/{bot_instance_id}`,
`DELETE:${base}/api/bots/config/schema/{bot_name}/cache`,
`GET:${base}/api/admin/names`,
`GET:${base}/api/admin/session_metrics`,
`GET:${base}/api/admin/validate_sessions`,
`GET:${base}/api/bots`,
`GET:${base}/api/bots/config/lobby/{lobby_id}`,
`GET:${base}/api/bots/config/lobby/{lobby_id}/bot/{bot_name}`,
`GET:${base}/api/bots/config/lobby/{lobby_id}/bot/{bot_instance_id}`,
`GET:${base}/api/bots/config/schema/{bot_name}`,
`GET:${base}/api/bots/config/statistics`,
`GET:${base}/api/bots/instances/{bot_instance_id}`,

View File

@ -127,17 +127,17 @@ export interface paths {
*/
delete: operations["delete_lobby_configs_ai_voicebot_api_bots_config_lobby__lobby_id__delete"];
};
"/ai-voicebot/api/bots/config/lobby/{lobby_id}/bot/{bot_name}": {
"/ai-voicebot/api/bots/config/lobby/{lobby_id}/bot/{bot_instance_id}": {
/**
* Get Lobby Bot Config
* @description Get specific bot configuration for a lobby
*/
get: operations["get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__get"];
get: operations["get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_instance_id__get"];
/**
* Delete Bot Config
* @description Delete bot configuration for a lobby
*/
delete: operations["delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__delete"];
delete: operations["delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_instance_id__delete"];
};
"/ai-voicebot/api/bots/config/update": {
/**
@ -402,9 +402,11 @@ export interface components {
*/
required?: boolean;
/** Options */
options?: {
[key: string]: string;
}[] | null;
options?:
| {
[key: string]: string;
}[]
| null;
/** Min Value */
min_value?: number | null;
/** Max Value */
@ -431,17 +433,19 @@ export interface components {
/** Parameters */
parameters: components["schemas"]["BotConfigParameter"][];
/** Categories */
categories?: {
[key: string]: string[];
}[] | null;
categories?:
| {
[key: string]: string[];
}[]
| null;
};
/**
* BotConfigUpdateRequest
* @description Request to update bot configuration
*/
BotConfigUpdateRequest: {
/** Bot Name */
bot_name: string;
/** Bot Instance Id */
bot_instance_id: string;
/** Lobby Id */
lobby_id: string;
/** Config Values */
@ -793,7 +797,6 @@ export type $defs = Record<string, never>;
export type external = Record<string, never>;
export interface operations {
/**
* System Health
* @description System health check showing manager status and enhanced monitoring
@ -1225,11 +1228,11 @@ export interface operations {
* Get Lobby Bot Config
* @description Get specific bot configuration for a lobby
*/
get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__get: {
get_lobby_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_instance_id__get: {
parameters: {
path: {
lobby_id: string;
bot_name: string;
bot_instance_id: string;
};
};
responses: {
@ -1251,11 +1254,11 @@ export interface operations {
* Delete Bot Config
* @description Delete bot configuration for a lobby
*/
delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_name__delete: {
delete_bot_config_ai_voicebot_api_bots_config_lobby__lobby_id__bot__bot_instance_id__delete: {
parameters: {
path: {
lobby_id: string;
bot_name: string;
bot_instance_id: string;
};
};
responses: {

View File

@ -52,6 +52,7 @@ services:
- ./shared:/shared:ro
- ./server:/server:rw
- ./server/.venv:/server/.venv:rw
- ./server/entrypoint.sh:/entrypoint.sh:ro
- ./client/build:/client/build:ro
- ./dev-keys:/keys:ro
- /etc/localtime:/etc/localtime:ro
@ -79,6 +80,7 @@ services:
- ./shared:/shared:ro
- ./voicebot:/voicebot:rw
- ./voicebot/.venv:/voicebot/.venv:rw
- ./voicebot/entrypoint.sh:/entrypoint.sh:ro
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
# network_mode: host

View File

@ -51,11 +51,13 @@ except ImportError:
def create_bot_config_router(
config_manager: BotConfigManager, bot_manager: BotManager
config_manager: BotConfigManager, bot_manager: BotManager, public_url: str = "/"
) -> APIRouter:
"""Create FastAPI router for bot configuration endpoints"""
router = APIRouter(prefix="/api/bots/config", tags=["Bot Configuration"])
router = APIRouter(
prefix=f"{public_url}api/bots/config", tags=["Bot Configuration"]
)
@router.get("/schema/{bot_name}")
async def get_bot_config_schema(bot_name: str) -> BotConfigSchema: # type: ignore
@ -137,24 +139,41 @@ def create_bot_config_router(
logger.error(f"Failed to get lobby configs for {lobby_id}: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/lobby/{lobby_id}/bot/{bot_name}")
async def get_lobby_bot_config(lobby_id: str, bot_name: str) -> BotLobbyConfig:
@router.get("/lobby/{lobby_id}/bot/{bot_instance_id}")
async def get_lobby_bot_config(
lobby_id: str, bot_instance_id: str
) -> BotLobbyConfig:
"""Get specific bot configuration for a lobby"""
logger.info(
f"Route handler called: get_lobby_bot_config({lobby_id}, {bot_instance_id})"
)
try:
# Get bot instance to find the bot_name
bot_instance = await bot_manager.get_bot_instance(bot_instance_id)
bot_name = bot_instance.bot_name
config = config_manager.get_lobby_bot_config(lobby_id, bot_name)
if not config:
logger.warning(
f"No configuration found for bot instance '{bot_instance_id}' (bot: '{bot_name}') in lobby '{lobby_id}'"
)
raise HTTPException(
status_code=404,
detail=f"No configuration found for bot '{bot_name}' in lobby '{lobby_id}'",
detail=f"No configuration found for bot instance '{bot_instance_id}' in lobby '{lobby_id}'",
)
return config
except ValueError:
# Bot instance not found
raise HTTPException(
status_code=404, detail=f"Bot instance '{bot_instance_id}' not found"
)
except HTTPException:
raise
except Exception as e:
logger.error(
f"Failed to get config for bot {bot_name} in lobby {lobby_id}: {e}"
f"Failed to get config for bot instance {bot_instance_id} in lobby {lobby_id}: {e}"
)
raise HTTPException(status_code=500, detail="Internal server error")
@ -165,54 +184,39 @@ def create_bot_config_router(
session_id: str = "unknown", # TODO: Get from auth/session context
) -> BotConfigUpdateResponse:
"""Update bot configuration for a lobby"""
logger.info(
f"Route handler called: update_bot_config({request.bot_instance_id}, {request.lobby_id})"
)
try:
# Find the provider for this bot
provider_id = None
provider_url = None
# Get bot instance information
bot_instance = await bot_manager.get_bot_instance(request.bot_instance_id)
bot_name = bot_instance.bot_name
provider_id = bot_instance.provider_id
providers_response = bot_manager.list_providers()
for provider in providers_response.providers:
try:
provider_bots = await bot_manager.get_provider_bots(
provider.provider_id
)
bot_names = [bot.name for bot in provider_bots.bots]
if request.bot_name in bot_names:
provider_id = provider.provider_id
provider_url = provider.base_url
break
except Exception:
continue
if not provider_id:
# Find the provider URL
provider = bot_manager.get_provider(provider_id)
if not provider:
raise HTTPException(
status_code=404,
detail=f"Bot '{request.bot_name}' not found in any provider"
status_code=404, detail=f"Provider '{provider_id}' not found"
)
provider_url = provider.base_url
# Update configuration
if not provider_id or not provider_url:
raise HTTPException(
status_code=404,
detail=f"Bot {request.bot_name} not found in any registered provider",
)
config = config_manager.set_bot_config(
lobby_id=request.lobby_id,
bot_name=request.bot_name,
bot_name=bot_name,
provider_id=provider_id,
config_values=request.config_values,
session_id=session_id
session_id=session_id,
)
# Notify bot provider in background
background_tasks.add_task(
config_manager.notify_bot_config_change,
provider_url,
request.bot_name,
bot_name,
request.lobby_id,
config
config,
)
return BotConfigUpdateResponse(
@ -222,31 +226,39 @@ def create_bot_config_router(
)
except ValueError as e:
# Validation error
# Bot instance not found
return BotConfigUpdateResponse(
success=False,
message=f"Validation error: {str(e)}"
success=False, message=f"Bot instance not found: {str(e)}"
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to update bot config: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/lobby/{lobby_id}/bot/{bot_name}")
async def delete_bot_config(lobby_id: str, bot_name: str) -> Dict[str, Any]:
@router.delete("/lobby/{lobby_id}/bot/{bot_instance_id}")
async def delete_bot_config(lobby_id: str, bot_instance_id: str) -> Dict[str, Any]:
"""Delete bot configuration for a lobby"""
try:
# Get bot instance to find the bot_name
bot_instance = await bot_manager.get_bot_instance(bot_instance_id)
bot_name = bot_instance.bot_name
success = config_manager.delete_bot_config(lobby_id, bot_name)
if not success:
raise HTTPException(
status_code=404,
detail=f"No configuration found for bot '{bot_name}' in lobby '{lobby_id}'"
detail=f"No configuration found for bot instance '{bot_instance_id}' in lobby '{lobby_id}'",
)
return {"success": True, "message": "Configuration deleted successfully"}
except ValueError:
# Bot instance not found
raise HTTPException(
status_code=404, detail=f"Bot instance '{bot_instance_id}' not found"
)
except HTTPException:
raise
except Exception as e:
@ -328,8 +340,13 @@ def create_bot_config_router(
# Find the provider for this bot
providers_response = bot_manager.list_providers()
for provider in providers_response.providers:
for provider_public in providers_response.providers:
try:
# Get the full provider object to access base_url
provider = bot_manager.get_provider(provider_public.provider_id)
if not provider:
continue
provider_bots = await bot_manager.get_provider_bots(
provider.provider_id
)
@ -353,7 +370,7 @@ def create_bot_config_router(
except Exception as e:
logger.warning(
f"Failed to check provider {provider.provider_id}: {e}"
f"Failed to check provider {provider_public.provider_id}: {e}"
)
continue

View File

@ -20,10 +20,12 @@ except ImportError as e:
)
def create_bot_router(bot_manager, session_manager, lobby_manager):
def create_bot_router(
bot_manager, session_manager, lobby_manager, public_url: str = "/"
) -> APIRouter:
"""Create bot API router with dependencies"""
router = APIRouter(prefix="/bots", tags=["bots"])
router = APIRouter(prefix=f"{public_url}api/bots", tags=["bots"])
@router.post("/providers/register", response_model=BotProviderRegisterResponse)
async def register_bot_provider(request: BotProviderRegisterRequest) -> BotProviderRegisterResponse:

0
server/entrypoint.sh Normal file → Executable file
View File

View File

@ -19,6 +19,7 @@ import os
import asyncio
from contextlib import asynccontextmanager
from datetime import datetime
import time
from fastapi import FastAPI, WebSocket, Path, Request
from fastapi.responses import Response
@ -64,10 +65,14 @@ try:
from api.monitoring import router as monitoring_router
from core.performance import metrics_collector
from core.health import (
health_monitor, DatabaseHealthCheck, WebSocketHealthCheck,
LobbyHealthCheck, SystemResourceHealthCheck
health_monitor,
DatabaseHealthCheck,
WebSocketHealthCheck,
LobbyHealthCheck,
SystemResourceHealthCheck,
)
from core.cache import cache_manager
monitoring_available = True
logger.info("Performance monitoring modules loaded successfully")
except ImportError as e:
@ -100,6 +105,27 @@ app = FastAPI(
redoc_url=redoc_url,
)
@app.middleware("http")
async def log_requests(request: Request, call_next): # type: ignore
start_time = time.time()
# Log information before the request is processed
logger.info(
f"Incoming request: Method={request.method}, URL={request.url.path}, Client={request.client.host}"
)
response = await call_next(request)
# Log information after the response is generated
process_time = time.time() - start_time
logger.info(
f"Outgoing response: Status={response.status_code}, Time Taken={process_time:.4f}s"
)
return response
logger.info(f"Starting server with public URL: {public_url}")
@ -123,7 +149,6 @@ async def lifespan(app: FastAPI):
bot_config_manager, \
websocket_manager
# Startup
logger.info("Starting AI Voice Bot server with modular architecture...")
@ -177,18 +202,36 @@ async def lifespan(app: FastAPI):
)
# Create bot API router
bot_router = create_bot_router(bot_manager, session_manager, lobby_manager)
bot_router = create_bot_router(
bot_manager, session_manager, lobby_manager, public_url=public_url
)
# Create bot configuration API router
bot_config_router = create_bot_config_router(bot_config_manager, bot_manager)
bot_config_router = create_bot_config_router(
bot_config_manager, bot_manager, public_url=public_url
)
logger.info(
f"Bot config router created with {len(bot_config_router.routes)} routes"
)
# Register API routes during startup
app.include_router(admin_api.router)
app.include_router(session_api.router)
app.include_router(lobby_api.router)
app.include_router(bot_router, prefix=public_url.rstrip("/") + "/api")
app.include_router(bot_config_router, prefix=public_url.rstrip("/"))
app.include_router(bot_router)
try:
app.include_router(bot_config_router)
logger.info(
f"Bot config router included successfully with {len(bot_config_router.routes)} routes"
)
logger.info(f"Bot config router prefix: {bot_config_router.prefix}")
except Exception as e:
logger.error(f"Failed to include bot config router: {e}")
raise
logger.info(f"Total routes in app after inclusion: {len(app.routes)}")
# Add monitoring router if available
if monitoring_available and monitoring_router:
app.include_router(monitoring_router, prefix=public_url.rstrip("/"))
@ -197,14 +240,16 @@ async def lifespan(app: FastAPI):
# Initialize and start performance monitoring if available
if monitoring_available:
logger.info("Starting performance monitoring...")
# Register health check components
if health_monitor:
health_monitor.register_component(DatabaseHealthCheck(session_manager))
health_monitor.register_component(WebSocketHealthCheck(session_manager))
health_monitor.register_component(LobbyHealthCheck(lobby_manager))
health_monitor.register_component(SystemResourceHealthCheck(metrics_collector))
health_monitor.register_component(
SystemResourceHealthCheck(metrics_collector)
)
# Start monitoring tasks
if metrics_collector:
await metrics_collector.start_collection()
@ -214,7 +259,7 @@ async def lifespan(app: FastAPI):
await cache_manager.start_all()
# Warm up caches with current data
await cache_manager.warm_cache(session_manager, lobby_manager)
logger.info("Performance monitoring started successfully!")
else:
logger.info("Performance monitoring disabled - running in basic mode")
@ -383,26 +428,28 @@ async def system_health():
"bot_manager": "active" if bot_manager else "inactive",
"websocket_manager": "active" if websocket_manager else "inactive",
}
# Get enhanced monitoring status
monitoring_status = {
"performance_monitoring": "active" if metrics_collector else "inactive",
"health_monitoring": "active" if health_monitor else "inactive",
"cache_management": "active" if cache_manager else "inactive",
}
# Get basic statistics
statistics = {
"sessions": session_manager.get_session_count() if session_manager else 0,
"lobbies": lobby_manager.get_lobby_count() if lobby_manager else 0,
"protected_names": auth_manager.get_protection_count() if auth_manager else 0,
"protected_names": auth_manager.get_protection_count()
if auth_manager
else 0,
}
# Get performance metrics if available
performance_summary = {}
if metrics_collector:
performance_summary = metrics_collector.get_performance_summary()
return {
"status": "ok",
"architecture": "modular_with_monitoring",
@ -410,16 +457,18 @@ async def system_health():
"managers": manager_status,
"monitoring": monitoring_status,
"statistics": statistics,
"performance": performance_summary.get("health_status", "unknown") if performance_summary else "unknown",
"timestamp": datetime.now().isoformat()
"performance": performance_summary.get("health_status", "unknown")
if performance_summary
else "unknown",
"timestamp": datetime.now().isoformat(),
}
except Exception as e:
logger.error(f"Error in system health check: {e}")
return {
"status": "error",
"message": str(e),
"timestamp": datetime.now().isoformat()
"timestamp": datetime.now().isoformat(),
}

View File

@ -126,8 +126,8 @@ def _setup_logging(level: str=logging_level) -> logging.Logger:
"uvicorn",
"uvicorn.error",
"uvicorn.access",
"fastapi",
"starlette",
# "fastapi",
# "starlette",
):
logger = logging.getLogger(noisy_logger)
logger.setLevel(logging.WARNING)

View File

@ -402,7 +402,7 @@ class BotLobbyConfig(BaseModel):
class BotConfigUpdateRequest(BaseModel):
"""Request to update bot configuration"""
bot_name: str
bot_instance_id: str
lobby_id: str
config_values: Dict[str, Any]
@ -467,7 +467,7 @@ class BotProviderPublicModel(BaseModel):
last_seen: float
class BotProviderListResponse(BaseModel):
class kBotProviderListResponse(BaseModel):
"""Response listing all registered bot providers"""
providers: List[BotProviderPublicModel]

View File

@ -243,12 +243,16 @@ def discover_bots() -> "List[BotInfoModel]":
if hasattr(mod, "agent_info") and callable(getattr(mod, "agent_info")):
try:
info = mod.agent_info()
# Convert string has_media to boolean for compatibility
# Convert string has_media and configurable to boolean for compatibility
processed_info = dict(info)
has_media_value = processed_info.get("has_media", True)
if isinstance(has_media_value, str):
processed_info["has_media"] = has_media_value.lower() in ("true", "1", "yes")
configurable_value = processed_info.get("configurable", False)
if isinstance(configurable_value, str):
processed_info["configurable"] = configurable_value.lower() in ("true", "1", "yes")
# Create BotInfoModel using model_validate
bot_info = BotInfoModel.model_validate(processed_info)
bots.append(bot_info)
@ -279,6 +283,8 @@ def discover_bots() -> "List[BotInfoModel]":
try:
config_schema = mod.get_config_schema()
logger.info(f"Bot {bot_info.name} supports configuration")
# Set the config_schema on the BotInfoModel
bot_info.config_schema = config_schema
except Exception as e:
logger.warning(f"Failed to get config schema for {bot_info.name}: {e}")

0
voicebot/entrypoint.sh Normal file → Executable file
View File

View File

@ -5,12 +5,164 @@ description = "AI Voicebot Environment"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"about-time>=4.2.1",
"aiofiles>=24.1.0",
"aiohappyeyeballs>=2.6.1",
"aiohttp>=3.12.15",
"aioice>=0.10.1",
"aiortc>=1.13.0",
"numpy<2.3",
"opencv-python>=4.11.0.86",
"pydantic>=2.11.7",
"aiosignal>=1.4.0",
"alive-progress>=3.2.0",
"annotated-types>=0.7.0",
"anyio>=4.10.0",
"attrs>=25.3.0",
"audioread>=3.0.1",
"autograd>=1.8.0",
"av>=14.4.0",
"brotli>=1.1.0",
"certifi>=2025.8.3",
"cffi>=2.0.0",
"charset-normalizer>=3.4.3",
"click>=8.2.1",
"cma>=4.3.0",
"contourpy>=1.3.3",
"cryptography>=45.0.7",
"cycler>=0.12.1",
"datasets>=4.1.0",
"decorator>=5.2.1",
"deprecated>=1.2.18",
"dill>=0.4.0",
"dnspython>=2.8.0",
"fastapi>=0.116.1",
"ffmpy>=0.6.1",
"filelock>=3.19.1",
"fonttools>=4.59.2",
"frozenlist>=1.7.0",
"fsspec>=2025.9.0",
"google-crc32c>=1.7.1",
"gradio>=5.45.0",
"gradio-client>=1.13.0",
"graphemeu>=0.8.0",
"groovy>=0.1.2",
"h11>=0.16.0",
"hf-xet>=1.1.10",
"httpcore>=1.0.9",
"httpx>=0.28.1",
"huggingface-hub>=0.34.5",
"idna>=3.10",
"ifaddr>=0.2.0",
"iniconfig>=2.1.0",
"jinja2>=3.1.6",
"jiwer>=4.0.0",
"joblib>=1.5.2",
"jsonschema>=4.25.1",
"jsonschema-specifications>=2025.9.1",
"kiwisolver>=1.4.9",
"lazy-loader>=0.4",
"librosa>=0.11.0",
"llvmlite>=0.44.0",
"markdown-it-py>=4.0.0",
"markupsafe>=3.0.2",
"matplotlib>=3.10.6",
"mdurl>=0.1.2",
"ml-dtypes>=0.5.3",
"more-itertools>=10.8.0",
"mpmath>=1.3.0",
"msgpack>=1.1.1",
"multidict>=6.6.4",
"multiprocess>=0.70.16",
"natsort>=8.4.0",
"networkx>=3.4.2",
"ninja>=1.13.0",
"nncf>=2.18.0",
"numba>=0.61.2",
"numpy>=2.2.6",
"onnx>=1.19.0",
"openai-whisper",
"opencv-python>=4.12.0.88",
"openvino>=2025.3.0",
"openvino-genai>=2025.3.0.0",
"openvino-telemetry>=2025.2.0",
"openvino-tokenizers>=2025.3.0.0",
"optimum>=1.27.0",
"optimum-intel",
"orjson>=3.11.3",
"packaging>=25.0",
"pandas>=2.3.2",
"pillow>=11.3.0",
"platformdirs>=4.4.0",
"pluggy>=1.6.0",
"pooch>=1.8.2",
"propcache>=0.3.2",
"protobuf>=6.32.1",
"psutil>=7.0.0",
"pyarrow>=21.0.0",
"pycparser>=2.23",
"pydantic>=2.11.9",
"pydantic-core>=2.33.2",
"pydot>=3.0.4",
"pydub>=0.25.1",
"pyee>=13.0.0",
"pygments>=2.19.2",
"pylibsrtp>=0.12.0",
"pymoo>=0.6.1.5",
"pyopencl>=2025.2.6",
"pyopenssl>=25.2.0",
"pyparsing>=3.2.4",
"pytest>=8.4.2",
"pytest-asyncio>=1.2.0",
"python-dateutil>=2.9.0.post0",
"python-ffmpeg>=2.0.12",
"python-multipart>=0.0.20",
"pytools>=2025.2.4",
"pytz>=2025.2",
"pyyaml>=6.0.2",
"rapidfuzz>=3.14.1",
"referencing>=0.36.2",
"regex>=2025.9.1",
"requests>=2.32.5",
"resampy>=0.4.3",
"rich>=14.1.0",
"rpds-py>=0.27.1",
"ruff>=0.13.0",
"safehttpx>=0.1.6",
"safetensors>=0.6.2",
"scikit-learn>=1.7.2",
"scipy>=1.16.2",
"semantic-version>=2.10.0",
"setuptools>=80.9.0",
"shellingham>=1.5.4",
"siphash24>=1.8",
"six>=1.17.0",
"sniffio>=1.3.1",
"soundfile>=0.13.1",
"soxr>=1.0.0",
"speechrecognition>=3.14.3",
"starlette>=0.47.3",
"sympy>=1.14.0",
"tabulate>=0.9.0",
"threadpoolctl>=3.6.0",
"tiktoken>=0.11.0",
"tokenizers>=0.21.4",
"tomlkit>=0.13.3",
"torch>=2.8.0",
"torchvision>=0.23.0",
"tqdm>=4.67.1",
"transformers>=4.53.3",
"triton>=3.4.0",
"typer>=0.17.4",
"typing-extensions>=4.15.0",
"typing-inspection>=0.4.1",
"tzdata>=2025.2",
"urllib3>=2.5.0",
"uvicorn>=0.35.0",
"watchdog>=6.0.0",
"websockets>=15.0.1",
"librosa>=0.10.0",
"torch>=2.0.0",
"openai-whisper>=20231117",
"wrapt>=1.17.3",
"xxhash>=3.5.0",
"yarl>=1.20.1",
]
[tool.uv.sources]
openai-whisper = { git = "https://github.com/openai/whisper.git", rev = "c0d2f624c09dc18e709e37c2ad90c039a4eb72a2" }
optimum-intel = { git = "https://github.com/huggingface/optimum-intel.git", rev = "b9c151fec6b414d9ca78be8643d08e267b133bfc" }

View File

@ -1,171 +1,156 @@
about-time==4.2.1
-e file:///voicebot
aiofiles==24.1.0
aiohappyeyeballs==2.6.1
aiohttp==3.12.15
aioice==0.10.1
aiortc==1.13.0
aiosignal==1.4.0
alive-progress==3.3.0
annotated-types==0.7.0
anyio==4.10.0
attrs==25.3.0
audioread==3.0.1
autograd==1.8.0
av==14.4.0
brotli==1.1.0
certifi==2025.8.3
cffi==1.17.1
charset-normalizer==3.4.3
click==8.2.1
cma==4.3.0
contourpy==1.3.3
cryptography==45.0.7
cycler==0.12.1
datasets==4.0.0
decorator==5.2.1
deprecated==1.2.18
dill==0.3.8
dnspython==2.7.0
fastapi==0.116.1
ffmpy==0.6.1
filelock==3.19.1
fonttools==4.59.2
frozenlist==1.7.0
fsspec==2025.3.0
google-crc32c==1.7.1
gradio==5.44.1
gradio-client==1.12.1
graphemeu==0.7.2
groovy==0.1.2
h11==0.16.0
hf-xet==1.1.9
httpcore==1.0.9
httpx==0.28.1
huggingface-hub==0.34.4
idna==3.10
ifaddr==0.2.0
iniconfig==2.1.0
jinja2==3.1.6
jiwer==4.0.0
joblib==1.5.2
jsonschema==4.25.1
jsonschema-specifications==2025.9.1
kiwisolver==1.4.9
lazy-loader==0.4
librosa==0.11.0
llvmlite==0.44.0
markdown-it-py==4.0.0
markupsafe==3.0.2
matplotlib==3.10.6
mdurl==0.1.2
ml-dtypes==0.5.3
more-itertools==10.8.0
mpmath==1.3.0
msgpack==1.1.1
multidict==6.6.4
multiprocess==0.70.16
natsort==8.4.0
networkx==3.5
ninja==1.13.0
nncf==2.18.0
numba==0.61.2
numpy==2.2.6
nvidia-cublas-cu12==12.8.4.1
nvidia-cuda-cupti-cu12==12.8.90
nvidia-cuda-nvrtc-cu12==12.8.93
nvidia-cuda-runtime-cu12==12.8.90
nvidia-cudnn-cu12==9.10.2.21
nvidia-cufft-cu12==11.3.3.83
nvidia-cufile-cu12==1.13.1.3
nvidia-curand-cu12==10.3.9.90
nvidia-cusolver-cu12==11.7.3.90
nvidia-cusparse-cu12==12.5.8.93
nvidia-cusparselt-cu12==0.7.1
nvidia-nccl-cu12==2.27.3
nvidia-nvjitlink-cu12==12.8.93
nvidia-nvtx-cu12==12.8.90
onnx==1.19.0
about-time
aiofiles
aiohappyeyeballs
aiohttp
aioice
aiortc
aiosignal
alive-progress
annotated-types
anyio
attrs
audioread
autograd
av
brotli
certifi
cffi
charset-normalizer
click
cma
contourpy
cryptography
cycler
datasets
decorator
deprecated
dill
dnspython
fastapi
ffmpy
filelock
fonttools
frozenlist
fsspec
google-crc32c
gradio
gradio-client
graphemeu
groovy
h11
hf-xet
httpcore
httpx
huggingface-hub
idna
ifaddr
iniconfig
jinja2
jiwer
joblib
jsonschema
jsonschema-specifications
kiwisolver
lazy-loader
librosa
llvmlite
markdown-it-py
markupsafe
matplotlib
mdurl
ml-dtypes
more-itertools
mpmath
msgpack
multidict
multiprocess
natsort
networkx
ninja
nncf
numba
numpy
onnx
openai-whisper @ git+https://github.com/openai/whisper.git@c0d2f624c09dc18e709e37c2ad90c039a4eb72a2
opencv-python==4.11.0.86
openvino==2025.3.0
openvino-genai==2025.3.0.0
openvino-telemetry==2025.2.0
openvino-tokenizers==2025.3.0.0
optimum==1.27.0
opencv-python
openvino
openvino-genai
openvino-telemetry
openvino-tokenizers
optimum
optimum-intel @ git+https://github.com/huggingface/optimum-intel.git@b9c151fec6b414d9ca78be8643d08e267b133bfc
orjson==3.11.3
packaging==25.0
pandas==2.3.2
pillow==11.3.0
platformdirs==4.4.0
pluggy==1.6.0
pooch==1.8.2
propcache==0.3.2
protobuf==6.32.0
psutil==7.0.0
pyarrow==21.0.0
pycparser==2.22
pydantic==2.11.7
pydantic-core==2.33.2
pydot==3.0.4
pydub==0.25.1
pyee==13.0.0
pygments==2.19.2
pylibsrtp==0.12.0
pymoo==0.6.1.5
pyopencl==2025.2.6
pyopenssl==25.1.0
pyparsing==3.2.3
pytest==8.4.2
pytest-asyncio==1.1.0
python-dateutil==2.9.0.post0
python-ffmpeg==1.0.16
python-multipart==0.0.20
pytools==2025.2.4
pytz==2025.2
pyyaml==6.0.2
rapidfuzz==3.14.0
referencing==0.36.2
regex==2025.9.1
requests==2.32.5
resampy==0.4.3
rich==14.1.0
rpds-py==0.27.1
ruff==0.12.11
safehttpx==0.1.6
safetensors==0.6.2
scikit-learn==1.7.1
scipy==1.16.1
semantic-version==2.10.0
setuptools==80.9.0
shellingham==1.5.4
siphash24==1.8
six==1.17.0
sniffio==1.3.1
soundfile==0.13.1
soxr==0.5.0.post1
speechrecognition==3.14.3
starlette==0.47.3
sympy==1.14.0
tabulate==0.9.0
threadpoolctl==3.6.0
tiktoken==0.11.0
tokenizers==0.21.4
tomlkit==0.13.3
torch==2.8.0
torchvision==0.23.0
tqdm==4.67.1
transformers==4.53.3
triton==3.4.0
typer==0.17.3
typing-extensions==4.15.0
typing-inspection==0.4.1
tzdata==2025.2
urllib3==2.5.0
uvicorn==0.35.0
watchdog==6.0.0
websockets==15.0.1
wrapt==1.17.3
xxhash==3.5.0
yarl==1.20.1
orjson
packaging
pandas
pillow
platformdirs
pluggy
pooch
propcache
protobuf
psutil
pyarrow
pycparser
pydantic
pydantic-core
pydot
pydub
pyee
pygments
pylibsrtp
pymoo
pyopencl
pyopenssl
pyparsing
pytest
pytest-asyncio
python-dateutil
python-ffmpeg
python-multipart
pytools
pytz
pyyaml
rapidfuzz
referencing
regex
requests
resampy
rich
rpds-py
ruff
safehttpx
safetensors
scikit-learn
scipy
semantic-version
setuptools
shellingham
siphash24
six
sniffio
soundfile
soxr
speechrecognition
starlette
sympy
tabulate
threadpoolctl
tiktoken
tokenizers
tomlkit
torch
torchvision
tqdm
transformers
triton
typer
typing-extensions
typing-inspection
tzdata
urllib3
uvicorn
watchdog
websockets
wrapt
xxhash
yarl

2837
voicebot/uv.lock generated

File diff suppressed because it is too large Load Diff