from __future__ import annotations import re def markdown_to_line(md_text: str) -> str: text = md_text text = re.sub(r"\*\*(.+?)\*\*", r"【\1】", text) text = re.sub(r"__([^_]+)__", r"【\1】", text) text = re.sub(r"\*([^*\n]+)\*", r"_\1_", text) text = re.sub(r"(?\s+(.+)$", r"┃ \1", text, flags=re.MULTILINE) text = re.sub(r"^-{3,}$", "────────────", text, flags=re.MULTILINE) text = re.sub(r"^[*-]\s+(.+)$", r"• \1", text, flags=re.MULTILINE) return text.strip() def extract_code_blocks(text: str) -> tuple[str, list[dict]]: code_blocks: list[dict] = [] def _extract(match: re.Match) -> str: language = match.group(1) or "" code = match.group(2) code_blocks.append({"language": language, "code": code[:2000]}) return "" cleaned = re.sub(r"```(\w*)\n(.*?)```", _extract, text, flags=re.DOTALL) return cleaned, code_blocks def extract_flex_from_markdown(text: str) -> tuple[str, list[dict]]: from yuxi.channel.extensions.line.flex_templates import create_code_block_card text, code_blocks = extract_code_blocks(text) text, table_flex_messages = convert_table_to_flex(text) flex_messages: list[dict] = list(table_flex_messages) for cb in code_blocks: flex_messages.append(create_code_block_card(cb["code"], cb["language"])) return text, flex_messages def convert_table_to_flex(text: str) -> tuple[str, list[dict]]: flex_messages: list[dict] = [] lines = text.split("\n") result: list[str] = [] in_table = False headers: list[str] = [] rows: list[list[str]] = [] for line in lines: stripped = line.strip() if stripped.startswith("|") and stripped.endswith("|"): cells = [c.strip() for c in stripped[1:-1].split("|")] if not in_table: headers = cells in_table = True continue if all(re.match(r"^[-:]+$", c) for c in cells): continue rows.append(cells) else: if in_table: if len(headers) == 2: flex_messages.append(_build_receipt_card(headers, rows)) elif len(headers) >= 3: flex_messages.append(_build_flex_table(headers, rows[:10])) headers = [] rows = [] in_table = False result.append(line) if in_table: if len(headers) == 2: flex_messages.append(_build_receipt_card(headers, rows)) elif len(headers) >= 3: flex_messages.append(_build_flex_table(headers, rows[:10])) return "\n".join(result), flex_messages def _build_receipt_card(headers: list[str], rows: list[list[str]]) -> dict: body_contents = [] for i, row in enumerate(rows): label = row[0] if len(row) > 0 else "" value = row[1] if len(row) > 1 else "" bg_color = "#FFFFFF" if i % 2 == 0 else "#FAFAFA" body_contents.append({ "type": "box", "layout": "horizontal", "backgroundColor": bg_color, "contents": [ {"type": "text", "text": label, "flex": 1, "wrap": True}, {"type": "text", "text": value, "flex": 1, "wrap": True, "align": "end"}, ], }) return { "type": "flex", "altText": headers[0] if headers else "Receipt", "contents": { "type": "bubble", "body": { "type": "box", "layout": "vertical", "contents": body_contents, }, }, } def _build_flex_table(headers: list[str], rows: list[list[str]]) -> dict: header_items = [] for h in headers: header_items.append({ "type": "text", "text": h, "weight": "bold", "flex": 1, "wrap": True, "size": "sm", }) body_contents = [ { "type": "box", "layout": "horizontal", "contents": header_items, }, {"type": "separator"}, ] for row in rows: row_items = [] for i, h in enumerate(headers): value = row[i] if i < len(row) else "" row_items.append({ "type": "text", "text": value, "flex": 1, "wrap": True, "size": "sm", }) body_contents.append({ "type": "box", "layout": "horizontal", "contents": row_items, }) body_contents.append({"type": "separator"}) return { "type": "flex", "altText": f"Table: {headers[0]}" if headers else "Table", "contents": { "type": "bubble", "body": { "type": "box", "layout": "vertical", "contents": body_contents[: len(body_contents) - 1] if body_contents else [], }, }, } def chunk_text(text: str, limit: int = 5000) -> list[str]: if len(text) <= limit: return [text] chunks: list[str] = [] while len(text) > limit: split_point = text.rfind("\n", 0, limit) if split_point == -1 or split_point < limit // 2: split_point = text.rfind(". ", 0, limit) if split_point == -1 or split_point < limit // 2: split_point = text.rfind(" ", 0, limit) if split_point == -1 or split_point < limit // 2: split_point = limit chunks.append(text[:split_point].strip()) text = text[split_point:].strip() if text: chunks.append(text) return chunks