refactor(bot)!: rename 'ai procurement' -> 'bot/' so the compose build context is committed
The bot service in docker-compose.yml builds from ./bot, but the agent code lived in 'ai procurement/' and ./bot was only a Windows junction on reza.g's machine (and was gitignored) — exactly as the team reported: 'bot/ was never committed — prod-v7's bot is un-buildable from the repo.' - junction removed; 'ai procurement/' renamed to 'bot/' via git mv (all 41 files keep their history as pure renames) - 'bot/' dropped from .gitignore (voice/ stays ignored — runtime voice notes; bot/.env stays ignored by bot/.gitignore) - compose needs no change: context: ./bot and ./bot/app now resolve to committed files - bot/INTEGRATION-REPORT.md added: full story (problem, root cause, git state, security note on the committed BALE_BOT_TOKEN / AI_API_KEY compose defaults, and usage instructions for the team) Verified: 41 renames staged with zero secret/cache files; voice/ and bot/.env still ignored.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""Bot configuration.
|
||||
|
||||
All settings can be overridden with environment variables prefixed with
|
||||
``PROCUREMENT_BOT_`` (e.g. ``PROCUREMENT_BOT_BOT_TOKEN=...``) or a local
|
||||
``.env`` file. Docker/compose passes them in directly (same convention as
|
||||
the api service's ``PROCUREMENT_*`` prefix).
|
||||
"""
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="PROCUREMENT_BOT_",
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
# Bale Bot API — Telegram-compatible clone hosted at tapi.bale.ai.
|
||||
BALE_API_BASE: str = "https://tapi.bale.ai"
|
||||
BOT_TOKEN: str = "" # from Bale's BotFather
|
||||
POLL_TIMEOUT_SECONDS: int = 25 # getUpdates long-poll window
|
||||
POLL_ERROR_SLEEP_SECONDS: float = 3.0
|
||||
|
||||
# ERP API (in-network service URL inside Docker).
|
||||
API_BASE_URL: str = "http://localhost:8000/api/v1"
|
||||
API_TIMEOUT_SECONDS: float = 30.0
|
||||
# Optional bearer for the day procurement endpoints start enforcing auth.
|
||||
SERVICE_TOKEN: str = ""
|
||||
|
||||
# Frontend URL (for opening Wizard from Bale bot)
|
||||
FRONTEND_URL: str = "http://localhost:8081"
|
||||
|
||||
# Security token for standalone wizard review URL
|
||||
DRAFT_TOKEN_SECRET: str = "procurement-draft-secret-change-in-prod-v7"
|
||||
DRAFT_TOKEN_EXPIRE_SECONDS: int = 7200 # 2 hours
|
||||
|
||||
# Persistent storage directory for draft attachments (proformas)
|
||||
ATTACHMENTS_DIR: str = "data/attachments/proformas"
|
||||
|
||||
# OpenAI-compatible AI provider (LLM), e.g. OpenAI / GLM / DeepSeek / OpenRouter.
|
||||
AI_BASE_URL: str = "" # e.g. https://api.openai.com/v1
|
||||
AI_API_KEY: str = ""
|
||||
LLM_MODEL: str = "gpt-4o-mini"
|
||||
LLM_TIMEOUT_SECONDS: float = 60.0
|
||||
|
||||
# STT providers — support multiple models/APIs in parallel.
|
||||
# 1. JSON list: [{"name": "m1", "base_url": "...", "api_key": "...", "model": "..."}, ...]
|
||||
STT_PROVIDERS_JSON: str = ""
|
||||
|
||||
# 2. Individual provider variables (up to 3 distinct STT models)
|
||||
STT_PROVIDER_1_NAME: str = "model-1"
|
||||
STT_PROVIDER_1_BASE_URL: str = ""
|
||||
STT_PROVIDER_1_API_KEY: str = ""
|
||||
STT_PROVIDER_1_MODEL: str = ""
|
||||
STT_PROVIDER_1_LANGUAGE: str = "fa"
|
||||
|
||||
STT_PROVIDER_2_NAME: str = "model-2"
|
||||
STT_PROVIDER_2_BASE_URL: str = ""
|
||||
STT_PROVIDER_2_API_KEY: str = ""
|
||||
STT_PROVIDER_2_MODEL: str = ""
|
||||
STT_PROVIDER_2_LANGUAGE: str = "fa"
|
||||
|
||||
STT_PROVIDER_3_NAME: str = "model-3"
|
||||
STT_PROVIDER_3_BASE_URL: str = ""
|
||||
STT_PROVIDER_3_API_KEY: str = ""
|
||||
STT_PROVIDER_3_MODEL: str = ""
|
||||
STT_PROVIDER_3_LANGUAGE: str = "fa"
|
||||
|
||||
# Legacy single STT provider fallback:
|
||||
STT_BASE_URL: str = ""
|
||||
STT_API_KEY: str = ""
|
||||
STT_MODEL: str = "whisper-1"
|
||||
STT_LANGUAGE: str = "fa"
|
||||
|
||||
@property
|
||||
def stt_base_url(self) -> str:
|
||||
return self.STT_BASE_URL or self.AI_BASE_URL
|
||||
|
||||
@property
|
||||
def stt_api_key(self) -> str:
|
||||
return self.STT_API_KEY or self.AI_API_KEY
|
||||
|
||||
@property
|
||||
def stt_providers(self) -> list[dict]:
|
||||
import json
|
||||
if self.STT_PROVIDERS_JSON:
|
||||
try:
|
||||
data = json.loads(self.STT_PROVIDERS_JSON)
|
||||
if isinstance(data, list) and len(data) > 0:
|
||||
return data
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check explicit STT_PROVIDER_1 / 2 / 3
|
||||
custom_list: list[dict] = []
|
||||
for idx in (1, 2, 3):
|
||||
b_url = getattr(self, f"STT_PROVIDER_{idx}_BASE_URL")
|
||||
key = getattr(self, f"STT_PROVIDER_{idx}_API_KEY")
|
||||
model = getattr(self, f"STT_PROVIDER_{idx}_MODEL")
|
||||
name = getattr(self, f"STT_PROVIDER_{idx}_NAME")
|
||||
lang = getattr(self, f"STT_PROVIDER_{idx}_LANGUAGE") or "fa"
|
||||
if b_url and key:
|
||||
custom_list.append({
|
||||
"name": name or f"model-{idx}",
|
||||
"base_url": b_url,
|
||||
"api_key": key,
|
||||
"model": model or self.STT_MODEL,
|
||||
"language": lang,
|
||||
"timeout": 30.0,
|
||||
})
|
||||
if custom_list:
|
||||
return custom_list
|
||||
|
||||
# Fallback to single legacy STT provider if set
|
||||
if self.stt_base_url and self.stt_api_key:
|
||||
return [{
|
||||
"name": "default",
|
||||
"base_url": self.stt_base_url,
|
||||
"api_key": self.stt_api_key,
|
||||
"model": self.STT_MODEL,
|
||||
"language": self.STT_LANGUAGE,
|
||||
"timeout": 30.0,
|
||||
}]
|
||||
return []
|
||||
|
||||
# PostgreSQL — same instance/database as the api service.
|
||||
DATABASE_URL: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/procurement"
|
||||
|
||||
# Chats that may use the bot without Casdoor linking (comma separated).
|
||||
ADMIN_CHAT_IDS: str = ""
|
||||
|
||||
# Drafts older than this are marked expired by the sweeper loop.
|
||||
DRAFT_TTL_HOURS: int = 24
|
||||
|
||||
LOG_LEVEL: str = "INFO"
|
||||
|
||||
@property
|
||||
def admin_chat_ids(self) -> set[int]:
|
||||
out: set[int] = set()
|
||||
for part in self.ADMIN_CHAT_IDS.replace(";", ",").split(","):
|
||||
part = part.strip()
|
||||
if part.isdigit():
|
||||
out.add(int(part))
|
||||
return out
|
||||
|
||||
|
||||
settings = Settings()
|
||||
Reference in New Issue
Block a user