Skip to content
This repository was archived by the owner on Mar 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -334,4 +334,7 @@ logs/temp/
logs/usage/
*.ignore.*
a.json
a.py
a.py
share/
jprq_/
jprq
4 changes: 2 additions & 2 deletions conf/config_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class OTELConfig:
bsp_export_timeout: int = 30000
traces_sampler: str = "parentbased_traceidratio"
traces_sampler_arg: float = 1.0
exporter_otlp_endpoint: str = "http://tempo:4318/v1/traces"
exporter_otlp_endpoint: str = "http://localhost:4318/v1/traces"
exporter_otlp_headers: Optional[str] = None
console_export: bool = False

Expand Down Expand Up @@ -72,7 +72,7 @@ def New() -> AppConfig:
traces_sampler=os.getenv("TRACES_SAMPLER", "parentbased_traceidratio"),
traces_sampler_arg=float(os.getenv("TRACES_SAMPLER_ARG", 1.0)),
exporter_otlp_endpoint=os.getenv(
"OTEL_EXPORTER_OTLP_ENDPOINT", "http://tempo:4318/v1/traces"
"OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318/v1/traces"
),
exporter_otlp_headers=os.getenv("EXPORTER_OTLP_HEADERS", None),
console_export=bool(os.getenv("CONSOLE_EXPORT", False)),
Expand Down
11 changes: 10 additions & 1 deletion conf/logger_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,17 @@ def setup_logging(logfile: str) -> structlog.stdlib.BoundLogger:
return get_logger()


logfile_path = "/var/ui_server/" # "/var/ui_server/"
logfile_path = "logs/" # "/var/ui_server/"
logfile = "ui_server.log"
if not os.path.exists(logfile_path):
os.mkdir(logfile_path)
logger = setup_logging(os.path.join(logfile_path, logfile))

logging.getLogger("opentelemetry").setLevel(logging.ERROR)
logging.getLogger("opentelemetry.sdk").setLevel(logging.ERROR)
logging.getLogger("opentelemetry.exporter").setLevel(logging.ERROR)

# Suppress specific loggers
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
logging.getLogger("fastapi").setLevel(logging.WARNING)
7 changes: 7 additions & 0 deletions functions_to_format/functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
send_money_to_someone_via_card,
pay_for_home_utility,
get_home_utility_suppliers,
get_receiver_by_card,
build_receiver_by_card_ui,
build_send_money_ui,
)
from .contact import build_contact_widget, get_contact
from .news import build_news_widget, get_news
Expand All @@ -34,6 +37,8 @@
"chatbot_answer": None,
"text_widget": None,
"buttons_widget": build_buttons_row,
"receiver_by_card": build_receiver_by_card_ui,
"send_money": build_send_money_ui,
}
functions_mapper = {
"get_balance": get_balance,
Expand All @@ -59,4 +64,6 @@
"get_home_utility_suppliers": get_home_utility_suppliers,
"human_approval": human_approval_requests,
"human_approval_request": human_approval_requests,
"get_receiver_by_card": get_receiver_by_card,
"receiver_by_card": get_receiver_by_card,
}
120 changes: 98 additions & 22 deletions functions_to_format/functions/balance.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pydivkit as dv
from pydivkit.core import Expr
import json
from .general import (
add_ui_to_widget,
Expand All @@ -7,10 +8,13 @@
build_buttons_row,
build_text_widget,
TextWidget,
create_feedback_variables,
create_success_container,
create_error_container,
)
from .general.utils import save_builder_output
from pydantic import BaseModel
from typing import List
from typing import List, Optional
from conf import logger
from models.build import BuildOutput
from models.context import Context, LoggerContext
Expand All @@ -20,6 +24,26 @@
import structlog


# Feedback texts for balance actions
BALANCE_FEEDBACK_TEXTS = {
LanguageOptions.RUSSIAN: {
"topup_success": "Пополнение выполнено",
"transfer_success": "Перевод выполнен",
"action_error": "Ошибка операции",
},
LanguageOptions.ENGLISH: {
"topup_success": "Top up completed",
"transfer_success": "Transfer completed",
"action_error": "Operation failed",
},
LanguageOptions.UZBEK: {
"topup_success": "To'ldirish bajarildi",
"transfer_success": "O'tkazma bajarildi",
"action_error": "Amal xatosi",
},
}


class CardInfo(BaseModel):
masked_card_pan: str
image_url: str
Expand Down Expand Up @@ -188,9 +212,10 @@ def get_home_balances(context: Context) -> BuildOutput:
chat_id = context.logger_context.chat_id
api_key = context.api_key
logger = context.logger_context.logger
for k, v in backend_output["services"].items():
backend_output[k] = v
del backend_output["services"]
if "services" in backend_output:
for k, v in backend_output["services"].items():
backend_output[k] = v
del backend_output["services"]

backend_data: HomeBalance = HomeBalance(**backend_output)

Expand Down Expand Up @@ -247,33 +272,55 @@ def get_balance(context: Context) -> BuildOutput:
api_key = context.api_key
logger = context.logger_context.logger

logger.info(f"Processing balance request for chat_id: {chat_id}")
logger.debug(f"Backend output type: {type(backend_output)}")

# Janis Rubins: use precompiled validator if available to avoid repeated validation overhead
# preprocess backend output:
output = [
llm_output,
]
backend_data: CardsBalanceResponse = CardsBalanceResponse(**backend_output)

try:
backend_data: CardsBalanceResponse = CardsBalanceResponse(**backend_output)
logger.info(
f"Successfully parsed backend data with {len(backend_data.body[0].cardList)} cards"
)
except Exception as e:
logger.error(f"Failed to parse backend output: {e}")
raise

backend_output_processed: List[CardInfo] = []
# logger.info(f"backend_output {backend_output} ----- type:{type(backend_output)}")
for i, card_info in enumerate(backend_data.body[0].cardList):
logger.debug(f"Processing card {i + 1}: {card_info.pan}")

balance_value = 0
if type(card_info.cardBalance.balance) is int:
balance_value = card_info.cardBalance.balance
logger.debug(f"Card {i + 1} balance (int): {balance_value}")
elif type(card_info.cardBalance.balance) is str:
balance_value = int(card_info.cardBalance.balance.replace(" ", ""))
logger.debug(f"Card {i + 1} balance (str->int): {balance_value}")
elif type(card_info.cardBalance.balance) is float:
balance_value = int(card_info.cardBalance.balance)

backend_output_processed.append(
CardInfo(
masked_card_pan=card_info.pan,
card_type=card_info.processingSystem,
balance=balance_value,
card_name=card_info.cardDetails.cardName,
cardColor=card_info.cardDetails.cardColor,
image_url=card_info.bankIcon.bankLogoMini,
)
logger.debug(f"Card {i + 1} balance (float->int): {balance_value}")

card_info_processed = CardInfo(
masked_card_pan=card_info.pan,
card_type=card_info.processingSystem,
balance=balance_value,
card_name=card_info.cardDetails.cardName,
cardColor=card_info.cardDetails.cardColor,
image_url=card_info.bankIcon.bankLogoMini,
)
backend_output_processed.append(card_info_processed)
logger.debug(
f"Added card: {card_info_processed.card_name} with balance {balance_value}"
)

logger.info(f"Processed {len(backend_output_processed)} cards successfully")

text_widget = TextWidget(
order=1,
values=[{"text": llm_output}],
Expand All @@ -289,6 +336,8 @@ def get_balance(context: Context) -> BuildOutput:
],
)

logger.debug(f"Created widgets: text_widget (order=1), cards_list (order=2)")

widgets = add_ui_to_widget(
{
build_text_widget: WidgetInput(
Expand All @@ -307,10 +356,18 @@ def get_balance(context: Context) -> BuildOutput:
},
version,
)

logger.info(f"Built {len(widgets)} UI widgets for version {version}")

output = BuildOutput(
widgets=[widget.model_dump(exclude_none=True) for widget in widgets],
widgets_count=len(widgets),
)

logger.info(
f"Balance processing completed successfully. Output contains {output.widgets_count} widgets"
)

save_builder_output(context, output)
return output

Expand Down Expand Up @@ -424,27 +481,46 @@ def account_block(
)


def action_button(text: str):
def action_button(
text: str,
action_type: str = "default",
language: LanguageOptions = LanguageOptions.RUSSIAN,
):
"""
Create an action button UI component.
Create an action button UI component with success/error feedback.

Args:
text (str): Button text
action_type (str): Type of action for payload
language (LanguageOptions): Language for localization

Returns:
dv.DivText: UI component for action button
dv.DivText: UI component for action button with feedback handling
"""
button_id = f"action-{text.lower().replace(' ', '_')}"

return dv.DivText(
text=text,
text_color="#2563EB",
height=dv.DivFixedSize(value=36),
alignment_horizontal=dv.DivAlignmentHorizontal.CENTER,
paddings=dv.DivEdgeInsets(top=8, bottom=8),
border=dv.DivBorder(corner_radius=8, stroke=dv.DivStroke(color="#3B82F6")),
action=dv.DivAction(
log_id=f"action-{text.lower()}",
url=f"div-action://{text.lower()}", # или https://example.com/action
),
actions=[
dv.DivAction(
log_id=button_id,
url=f"div-action://{text.lower().replace(' ', '_')}",
payload={
"action": action_type,
"button_text": text,
},
),
# Success feedback action
dv.DivAction(
log_id=f"{button_id}-success",
url="div-action://set_variable?name=balance_action_success&value=1",
),
],
)


Expand Down
Loading
Loading