Concept internals

การจัดรูปแบบ Markdown

OpenClaw จัดรูปแบบ Markdown ขาออกโดยแปลงเป็นตัวแทนระดับกลาง (IR) ที่ใช้ร่วมกันก่อนเรนเดอร์เอาต์พุตเฉพาะช่องทาง IR จะคงข้อความต้นฉบับไว้ครบถ้วนพร้อมเก็บช่วงของสไตล์/ลิงก์ เพื่อให้การแบ่งเป็นชิ้นและการเรนเดอร์สอดคล้องกันได้ในทุกช่องทาง

เป้าหมาย

  • ความสอดคล้อง: ขั้นตอนการแยกวิเคราะห์เดียว เรนเดอร์ได้หลายแบบ
  • การแบ่งเป็นชิ้นอย่างปลอดภัย: แยกข้อความก่อนเรนเดอร์ เพื่อไม่ให้การจัดรูปแบบแบบอินไลน์ขาดกลางระหว่างชิ้น
  • เหมาะกับช่องทาง: แมป IR เดียวกันไปยัง Slack mrkdwn, Telegram HTML และช่วงสไตล์ของ Signal ได้โดยไม่ต้องแยกวิเคราะห์ Markdown ซ้ำ

ไปป์ไลน์

  1. แยกวิเคราะห์ Markdown -> IR
    • IR คือข้อความล้วนพร้อมช่วงสไตล์ (ตัวหนา/ตัวเอียง/ขีดฆ่า/โค้ด/สปอยล์) และช่วงลิงก์
    • ออฟเซ็ตเป็นหน่วยโค้ด UTF-16 เพื่อให้ช่วงสไตล์ของ Signal ตรงกับ API ของมัน
    • ตารางจะถูกแยกวิเคราะห์เฉพาะเมื่อช่องทางเลือกใช้การแปลงตาราง
  2. แบ่ง IR เป็นชิ้น (จัดรูปแบบก่อน)
    • การแบ่งเป็นชิ้นเกิดขึ้นบนข้อความ IR ก่อนเรนเดอร์
    • การจัดรูปแบบแบบอินไลน์จะไม่ถูกแยกข้ามชิ้น ช่วงต่างๆ จะถูกตัดแบ่งตามแต่ละชิ้น
  3. เรนเดอร์ตามช่องทาง
    • Slack: โทเคน mrkdwn (ตัวหนา/ตัวเอียง/ขีดฆ่า/โค้ด), ลิงก์เป็น <url|label>
    • Telegram: แท็ก HTML (<b>, <i>, <s>, <code>, <pre><code>, <a href>)
    • Signal: ข้อความล้วน + ช่วง text-style; ลิงก์จะกลายเป็น label (url) เมื่อป้ายกำกับต่างจาก URL

ตัวอย่าง IR

อินพุต Markdown:

Hello **world** - see [docs](https://docs.openclaw.ai).

IR (แบบแผนภาพ):

{
  "text": "Hello world - see docs.",
  "styles": [{ "start": 6, "end": 11, "style": "bold" }],
  "links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
}

ใช้ที่ใด

  • อะแดปเตอร์ขาออกของ Slack, Telegram และ Signal เรนเดอร์จาก IR
  • ช่องทางอื่นๆ (WhatsApp, iMessage, Microsoft Teams, Discord) ยังใช้ข้อความล้วนหรือกฎการจัดรูปแบบของตนเอง โดยมีการแปลงตาราง Markdown ก่อนการแบ่งเป็นชิ้นเมื่อเปิดใช้งาน

การจัดการตาราง

ตาราง Markdown ไม่ได้รับการรองรับอย่างสม่ำเสมอในทุกไคลเอนต์แชต ใช้ markdown.tables เพื่อควบคุมการแปลงในแต่ละช่องทาง (และแต่ละบัญชี)

  • code: เรนเดอร์ตารางเป็นบล็อกโค้ด (ค่าเริ่มต้นสำหรับช่องทางส่วนใหญ่)
  • bullets: แปลงแต่ละแถวเป็นหัวข้อย่อย (ค่าเริ่มต้นสำหรับ Signal + WhatsApp)
  • off: ปิดการแยกวิเคราะห์และการแปลงตาราง ข้อความตารางดิบจะถูกส่งผ่านไป

คีย์การตั้งค่า:

channels:
  discord:
    markdown:
      tables: code
    accounts:
      work:
        markdown:
          tables: off

กฎการแบ่งเป็นชิ้น

  • ขีดจำกัดของชิ้นมาจากอะแดปเตอร์/การตั้งค่าของช่องทาง และจะถูกใช้กับข้อความ IR
  • รั้วโค้ดจะถูกรักษาไว้เป็นบล็อกเดียวพร้อมขึ้นบรรทัดใหม่ท้ายบล็อก เพื่อให้ช่องทางต่างๆ เรนเดอร์ได้ถูกต้อง
  • คำนำหน้ารายการและคำนำหน้า blockquote เป็นส่วนหนึ่งของข้อความ IR ดังนั้นการแบ่งเป็นชิ้นจะไม่ตัดกลางคำนำหน้า
  • สไตล์แบบอินไลน์ (ตัวหนา/ตัวเอียง/ขีดฆ่า/โค้ดอินไลน์/สปอยล์) จะไม่ถูกแยกข้ามชิ้น เรนเดอร์เรอร์จะเปิดสไตล์ใหม่ภายในแต่ละชิ้น

หากต้องการรายละเอียดเพิ่มเติมเกี่ยวกับพฤติกรรมการแบ่งเป็นชิ้นข้ามช่องทาง โปรดดู การสตรีม + การแบ่งเป็นชิ้น

นโยบายลิงก์

  • Slack: [label](url) -> <url|label>; URL เปล่ายังคงเป็น URL เปล่า Autolink จะถูกปิดระหว่างการแยกวิเคราะห์เพื่อหลีกเลี่ยงการลิงก์ซ้ำ
  • Telegram: [label](url) -> <a href="url">label</a> (โหมดแยกวิเคราะห์ HTML)
  • Signal: [label](url) -> label (url) เว้นแต่ว่าป้ายกำกับตรงกับ URL

สปอยล์

มาร์กเกอร์สปอยล์ (||spoiler||) จะถูกแยกวิเคราะห์เฉพาะสำหรับ Signal ซึ่งจะถูกแมปเป็นช่วงสไตล์ SPOILER ช่องทางอื่นจะถือว่าเป็นข้อความล้วน

วิธีเพิ่มหรืออัปเดตตัวจัดรูปแบบของช่องทาง

  1. แยกวิเคราะห์ครั้งเดียว: ใช้ตัวช่วย markdownToIR(...) ที่ใช้ร่วมกัน พร้อมตัวเลือกที่เหมาะกับช่องทาง (autolink, สไตล์หัวเรื่อง, คำนำหน้า blockquote)
  2. เรนเดอร์: ใช้งานเรนเดอร์เรอร์ด้วย renderMarkdownWithMarkers(...) และแมปมาร์กเกอร์สไตล์ (หรือช่วงสไตล์ของ Signal)
  3. แบ่งเป็นชิ้น: เรียก chunkMarkdownIR(...) ก่อนเรนเดอร์ จากนั้นเรนเดอร์แต่ละชิ้น
  4. เชื่อมอะแดปเตอร์: อัปเดตอะแดปเตอร์ขาออกของช่องทางให้ใช้ตัวแบ่งชิ้นและเรนเดอร์เรอร์ใหม่
  5. ทดสอบ: เพิ่มหรืออัปเดตการทดสอบรูปแบบ และการทดสอบการส่งมอบขาออกหากช่องทางใช้การแบ่งเป็นชิ้น

ข้อควรระวังทั่วไป

  • โทเคนวงเล็บมุมของ Slack (<@U123>, <#C123>, <https://...>) ต้องถูกรักษาไว้ และต้อง escape HTML ดิบอย่างปลอดภัย
  • Telegram HTML ต้อง escape ข้อความนอกแท็กเพื่อหลีกเลี่ยงมาร์กอัปที่เสีย
  • ช่วงสไตล์ของ Signal ขึ้นอยู่กับออฟเซ็ต UTF-16 อย่าใช้ออฟเซ็ตแบบ code point
  • รักษาการขึ้นบรรทัดใหม่ท้ายบล็อกสำหรับบล็อกโค้ดแบบมีรั้ว เพื่อให้มาร์กเกอร์ปิดอยู่บนบรรทัดของตัวเอง

ที่เกี่ยวข้อง