from __future__ import annotations import re import uuid from typing import Any from yuxi.channels.models import ChannelResponse, MessageType from .chunking import chunk_text def convert_markdown_tables(text: str) -> str: lines = text.split("\n") result: list[str] = [] i = 0 while i < len(lines): line = lines[i] if re.match(r"^\|.*\|$", line.strip()) and i + 1 < len(lines): next_line = lines[i + 1] if re.match(r"^\|[\s\-:|]+\|$", next_line.strip()): header = _parse_table_row(line) rows: list[list[str]] = [] i += 2 while i < len(lines) and re.match(r"^\|.*\|$", lines[i].strip()): rows.append(_parse_table_row(lines[i])) i += 1 result.extend(_table_to_text(header, rows)) continue result.append(line) i += 1 return "\n".join(result) def _parse_table_row(line: str) -> list[str]: return [cell.strip() for cell in line.strip().strip("|").split("|")] def _table_to_text(header: list[str], rows: list[list[str]]) -> list[str]: cols = len(header) col_widths = [len(h) for h in header] for row in rows: for c in range(min(cols, len(row))): col_widths[c] = max(col_widths[c], len(row[c])) header_line = " | ".join(h.ljust(col_widths[i]) for i, h in enumerate(header)) sep_line = "-+-".join("-" * col_widths[i] for i in range(cols)) result = [header_line, sep_line] for row in rows: result.append(" | ".join((row[c] if c < len(row) else "").ljust(col_widths[c]) for c in range(cols))) return result def format_outbound( response: ChannelResponse, bot_display_name: str, text_chunk_limit: int = 4000, markdown_config: dict[str, Any] | None = None, response_prefix: str = "", ) -> list[dict[str, Any]]: markdown_config = markdown_config or {} table_mode = markdown_config.get("table_mode", markdown_config.get("tableMode", "auto")) content = response.content.strip() if not content: raise ValueError("Empty message content, cannot send") if response_prefix: content = f"{response_prefix} {content}" if table_mode == "always": content = convert_markdown_tables(content) elif table_mode == "auto": content = convert_markdown_tables(content) chunks = chunk_text(content, limit=text_chunk_limit) results: list[dict[str, Any]] = [] for idx, chunk in enumerate(chunks): payload: dict[str, Any] = { "token": response.identity.channel_chat_id, "message": chunk, "actorDisplayName": bot_display_name, } if response.reply_to_message_id: payload["replyTo"] = response.reply_to_message_id ref_id = response.metadata.get("reference_id") if ref_id: payload["referenceId"] = f"{ref_id}-{idx}" if len(chunks) > 1 else ref_id else: payload["referenceId"] = f"fp-{uuid.uuid4().hex}-{idx}" if len(chunks) > 1 else f"fp-{uuid.uuid4().hex}" if response.message_type in (MessageType.IMAGE, MessageType.FILE, MessageType.AUDIO, MessageType.VIDEO): raise ValueError("Media messages must use send_media(), not send()") results.append(payload) return results def format_outbound_single(response: ChannelResponse, bot_display_name: str) -> dict[str, Any]: payloads = format_outbound(response, bot_display_name) return payloads[0] if payloads else {"token": response.identity.channel_chat_id, "message": ""}