diff --git a/.github/DISCUSSION_TEMPLATE/syslog-submit.yml b/.github/DISCUSSION_TEMPLATE/syslog-submit.yml
new file mode 100644
index 00000000..a0baaa94
--- /dev/null
+++ b/.github/DISCUSSION_TEMPLATE/syslog-submit.yml
@@ -0,0 +1,51 @@
+title: "BC-XXX-XXX๏ฝSYSLOG"
+labels: []
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ## ๐ก SYSLOG ่ชๅฉๆไบค / ๅนฟๆญๆ้ฎ
+
+ > ๅๅฎไบ๏ผๆๆฅๅฟ็ฒ่ดด่ฟๅป๏ผๅกซไธ้ฎ็ฎฑ๏ผ็นๆไบคใๆฐๅนฟๆญไผๅๅฐไฝ ้ฎ็ฎฑ้ใ
+ >
+ > ๆ้ฎ้ข๏ผๆ้ฎ้ข็ฒ่ดด่ฟๅป๏ผๅกซไธ้ฎ็ฎฑ๏ผ็นๆไบคใ่งฃ็ญไผๅๅฐไฝ ้ฎ็ฎฑ้ใ
+
+ **ๆ ้ขๆ ผๅผ**๏ผ`BC-XXX-XXX๏ฝSYSLOG` ๆ `BC-XXX-XXX๏ฝๆ้ฎ`
+
+ - type: input
+ id: broadcast_id
+ attributes:
+ label: "ๅนฟๆญ็ผๅท"
+ description: "ๅไธไฝ ๅฝๅๅนฟๆญ็็ผๅท๏ผไพๅฆ BC-M22-009-AW"
+ placeholder: "BC-M22-009-AW"
+ validations:
+ required: true
+
+ - type: dropdown
+ id: submit_type
+ attributes:
+ label: "็ฑปๅ"
+ description: "้ๆฉๆไบค็ฑปๅ"
+ options:
+ - SYSLOG
+ - ๆ้ฎ
+ validations:
+ required: true
+
+ - type: input
+ id: email
+ attributes:
+ label: "ไฝ ็้ฎ็ฎฑ"
+ description: "็จไบๆฅๆถๆฐๅนฟๆญๆ้ฎ้ข่งฃ็ญ"
+ placeholder: "your@email.com"
+ validations:
+ required: true
+
+ - type: textarea
+ id: content
+ attributes:
+ label: "ๅ
ๅฎน"
+ description: "็ฒ่ดดไฝ ็ SYSLOG ๅ
จๆ ๆ ้ฎ้ขๆ่ฟฐ"
+ placeholder: "ๅจ่ฟ้็ฒ่ดดไฝ ็ SYSLOG ๆ้ฎ้ข..."
+ validations:
+ required: true
diff --git a/.github/workflows/syslog-auto-pipeline.yml b/.github/workflows/syslog-auto-pipeline.yml
new file mode 100644
index 00000000..c2fa3898
--- /dev/null
+++ b/.github/workflows/syslog-auto-pipeline.yml
@@ -0,0 +1,366 @@
+name: SYSLOG Auto Pipeline
+# ๐ก SYSLOG ่ชๅฉๆไบค็ณป็ป ยท ๅ
จ่ชๅจ้ญ็ฏ
+#
+# ๅผๅ่
ๅจ GitHub Discussion ๆไบค SYSLOG ๆๆ้ฎ
+# โ Actions ่ชๅจ่งฃๆ โ ่ฐ็จ LLM API ๅค้ไบบๆ ผไฝ
+# โ ๅๅ
ฅ Notion ๅทฅๅ โ ๅ้ฎไปถ็ปๅผๅ่
โ Discussion ๅๅค
+#
+# ไพ่ต Secrets๏ผ
+# LLM_API_KEY ็ฌฌไธๆน LLM ๅนณๅฐๅฏ้ฅ๏ผๅฟ
้กป๏ผ
+# LLM_BASE_URL ็ฌฌไธๆน LLM ๅนณๅฐ API ๅฐๅ๏ผๅฟ
้กป๏ผๅฆ https://api.xxx.com/v1๏ผ
+# NOTION_API_TOKEN Notion API token
+# NOTION_TICKET_DB_ID ้็ ๅทฅๅๆฐๆฎๅบ ID
+# CORE_BRAIN_PAGE_ID ๆๅฅๆ ธๅฟๅคง่ v4.0 ้กต้ข ID๏ผv4.0 ๅ่ฎฎๅจๆๆณจๅ
ฅ๏ผ
+# PORTRAIT_DB_ID ๅผๅ่
ๅจๆ็ปๅๅบๆฐๆฎๅบ ID๏ผv4.0 ๅ่ฎฎๅจๆๆณจๅ
ฅ๏ผ
+# FINGERPRINT_DB_ID ๆจกๅๆ็บนๆณจๅ่กจๆฐๆฎๅบ ID๏ผv4.0 ๅ่ฎฎๅจๆๆณจๅ
ฅ๏ผ
+# SMTP_USER QQ ้ฎ็ฎฑๅฐๅ
+# SMTP_PASS QQ ้ฎ็ฎฑ SMTP ๆๆ็
+
+on:
+ discussion:
+ types: [created]
+
+jobs:
+ process:
+ name: ๐ก ๅค็ SYSLOG ๆไบค / ๅนฟๆญๆ้ฎ
+ runs-on: ubuntu-latest
+ if: contains(github.event.discussion.category.name, 'SYSLOG')
+ permissions:
+ contents: write
+ discussions: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install dependencies
+ run: npm ci --ignore-scripts
+
+ - name: ๐ Parse submission
+ id: parse
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const body = context.payload.discussion.body || '';
+ const title = context.payload.discussion.title || '';
+
+ // Parse structured fields from discussion body
+ function extractField(text, label) {
+ // Match form field format: ### Label\n\nValue
+ const regex = new RegExp('###\\s*' + label + '\\s*\\n+([^\\n#]+)', 'i');
+ const match = text.match(regex);
+ return match ? match[1].trim() : '';
+ }
+
+ const broadcastId = extractField(body, 'ๅนฟๆญ็ผๅท') || '';
+ const submitType = extractField(body, '็ฑปๅ') || '';
+ const email = extractField(body, 'ไฝ ็้ฎ็ฎฑ') || '';
+
+ // Extract content (everything after "### ๅ
ๅฎน")
+ const contentMatch = body.match(/###\s*ๅ
ๅฎน\s*\n+([\s\S]*?)$/i);
+ const content = contentMatch ? contentMatch[1].trim() : '';
+
+ // Determine type from title or body
+ let type = 'question'; // default
+ if (submitType.includes('SYSLOG') || title.includes('SYSLOG')) {
+ type = 'syslog';
+ } else if (submitType.includes('ๆ้ฎ') || title.includes('ๆ้ฎ')) {
+ type = 'question';
+ }
+
+ // Validate
+ if (!broadcastId) {
+ core.setFailed('โ ็ผบๅฐๅนฟๆญ็ผๅท๏ผ่ฏทๅจๆไบคๆถๅกซๅๅนฟๆญ็ผๅท');
+ return;
+ }
+ if (!email) {
+ core.setFailed('โ ็ผบๅฐ้ฎ็ฎฑ๏ผ่ฏทๅจๆไบคๆถๅกซๅไฝ ็้ฎ็ฎฑ');
+ return;
+ }
+ if (!content) {
+ core.setFailed('โ ็ผบๅฐๅ
ๅฎน๏ผ่ฏท็ฒ่ดดไฝ ็ SYSLOG ๆ้ฎ้ข');
+ return;
+ }
+
+ core.setOutput('broadcast_id', broadcastId);
+ core.setOutput('type', type);
+ core.setOutput('email', email);
+ core.setOutput('content', content);
+ core.setOutput('discussion_number', context.payload.discussion.number);
+ core.setOutput('author', context.payload.discussion.user.login);
+
+ console.log(`๐ก ่งฃๆๅฎๆ: ๅนฟๆญ=${broadcastId}, ็ฑปๅ=${type}, ้ฎ็ฎฑ=${email}`);
+
+ - name: ๐ง Auto-detect and wake up persona
+ id: persona
+ env:
+ LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
+ LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ SUBMIT_CONTENT: ${{ steps.parse.outputs.content }}
+ AUTHOR: ${{ steps.parse.outputs.author }}
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ CORE_BRAIN_PAGE_ID: ${{ secrets.CORE_BRAIN_PAGE_ID }}
+ PORTRAIT_DB_ID: ${{ secrets.PORTRAIT_DB_ID }}
+ FINGERPRINT_DB_ID: ${{ secrets.FINGERPRINT_DB_ID }}
+ run: node scripts/wake-persona.js
+
+ - name: ๐ Write to Notion ticket
+ if: ${{ secrets.NOTION_API_TOKEN }}
+ env:
+ NOTION_TOKEN: ${{ secrets.NOTION_API_TOKEN }}
+ NOTION_TICKET_DB_ID: ${{ secrets.NOTION_TICKET_DB_ID }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ run: |
+ node -e "
+ const https = require('https');
+
+ const token = process.env.NOTION_TOKEN;
+ const dbId = process.env.NOTION_TICKET_DB_ID;
+ const broadcastId = process.env.BROADCAST_ID || 'UNKNOWN';
+ const type = process.env.SUBMIT_TYPE || 'syslog';
+ const result = process.env.PERSONA_RESULT || '(no result)';
+
+ if (!token || !dbId) {
+ console.log('โ ๏ธ Notion credentials not configured, skipping ticket creation');
+ process.exit(0);
+ }
+
+ const typeLabel = type === 'syslog' ? 'SYSLOG้ญ็ฏ' : 'ๆ้ฎ่งฃ็ญ';
+ const title = '[่ชๅจ] ' + broadcastId + ' ยท ' + typeLabel;
+
+ const body = JSON.stringify({
+ parent: { database_id: dbId },
+ properties: {
+ 'ๆ ้ข': { title: [{ type: 'text', text: { content: title.slice(0, 120) } }] },
+ 'ๆไฝ็ฑปๅ': { select: { name: 'ๅ
ถไป' } },
+ 'ๆไบค่
': { rich_text: [{ type: 'text', text: { content: '่ชๅจ็ฎก้' } }] },
+ '็ถๆ': { select: { name: 'ๅทฒๅฎๆ' } },
+ 'ไผๅ
็บง': { select: { name: 'P1' } }
+ },
+ children: [{
+ object: 'block',
+ type: 'paragraph',
+ paragraph: {
+ rich_text: [{ type: 'text', text: { content: result.slice(0, 2000) } }]
+ }
+ }]
+ });
+
+ const opts = {
+ hostname: 'api.notion.com',
+ port: 443,
+ path: '/v1/pages',
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + token,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': '2022-06-28',
+ 'Content-Length': Buffer.byteLength(body)
+ }
+ };
+
+ const req = https.request(opts, (res) => {
+ let data = '';
+ res.on('data', (c) => data += c);
+ res.on('end', () => {
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ console.log('โ
Notion ๅทฅๅๅทฒๅๅปบ');
+ } else {
+ console.log('โ ๏ธ Notion ๅทฅๅๅๅปบๅคฑ่ดฅ: ' + res.statusCode + ' ' + data);
+ }
+ });
+ });
+ req.on('error', (e) => console.log('โ ๏ธ Notion ่ฏทๆฑๅคฑ่ดฅ: ' + e.message));
+ req.write(body);
+ req.end();
+ "
+
+ - name: ๐ง Send email to developer
+ env:
+ SMTP_USER: ${{ secrets.SMTP_USER }}
+ SMTP_PASS: ${{ secrets.SMTP_PASS }}
+ EMAIL_TO: ${{ steps.parse.outputs.email }}
+ BROADCAST_ID: ${{ steps.parse.outputs.broadcast_id }}
+ SUBMIT_TYPE: ${{ steps.parse.outputs.type }}
+ PERSONA_RESULT: ${{ steps.persona.outputs.result }}
+ run: |
+ node -e "
+ const nodemailer = require('nodemailer');
+
+ const user = process.env.SMTP_USER || '';
+ const pass = process.env.SMTP_PASS || '';
+ const to = process.env.EMAIL_TO || '';
+ const broadcastId = process.env.BROADCAST_ID || '';
+ const type = process.env.SUBMIT_TYPE || 'syslog';
+ const result = process.env.PERSONA_RESULT || '(ๅค็ไธญ๏ผ่ฏท็จๅ)';
+
+ if (!user || !pass) {
+ console.log('โ ๏ธ SMTP not configured, skipping email');
+ process.exit(0);
+ }
+ if (!to) {
+ console.log('โ ๏ธ No recipient email, skipping');
+ process.exit(0);
+ }
+
+ const subjectText = type === 'syslog'
+ ? '[ๅ
ๆน็ณป็ป] ' + broadcastId + ' ยท ๆฐๅนฟๆญๅทฒ็ๆ'
+ : '[ๅ
ๆน็ณป็ป] ' + broadcastId + ' ยท ้ฎ้ขๅทฒ่งฃ็ญ';
+
+ const resultHtml = result
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\n/g, '
');
+
+ const html = [
+ '
',
+ '
',
+ '
๐ ๅ
ๆน็ณป็ป ยท ่ชๅจ้็ฅ
',
+ '
HoloLake ยท ไบบๆ ผ่ฏญ่จๆไฝ็ณป็ป
',
+ '
',
+ '
',
+ '
๐ก ' + broadcastId + '
',
+ '
' + resultHtml + '
',
+ '
',
+ '
',
+ '
'
+ ].join('\n');
+
+ const transporter = nodemailer.createTransport({
+ host: 'smtp.qq.com',
+ port: 465,
+ secure: true,
+ auth: { user, pass }
+ });
+
+ transporter.sendMail({
+ from: '\"ๅ
ๆน็ณป็ป\" <' + user + '>',
+ to: to,
+ subject: subjectText,
+ html: html
+ }).then((info) => {
+ console.log('โ
้ฎไปถๅทฒๅ้: ' + info.messageId);
+ }).catch((err) => {
+ console.log('โ ๏ธ ้ฎไปถๅ้ๅคฑ่ดฅ: ' + err.message);
+ });
+ "
+
+ - name: ๐ฌ Reply to Discussion
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const number = ${{ steps.parse.outputs.discussion_number }};
+ const type = '${{ steps.parse.outputs.type }}';
+ const broadcastId = '${{ steps.parse.outputs.broadcast_id }}';
+ const email = '${{ steps.parse.outputs.email }}';
+
+ const typeLabel = type === 'syslog' ? 'SYSLOG ้ญ็ฏๅค็' : '้ฎ้ข่งฃ็ญ';
+ const maskedEmail = email.length > 4
+ ? email.replace(/(.{2})(.*)(@.*)/, '$1***$3')
+ : '***';
+
+ const body = [
+ 'โ
**ๅทฒๅค็** ยท ' + typeLabel,
+ '',
+ '| ้กน็ฎ | ๅ
ๅฎน |',
+ '|------|------|',
+ '| ๐ก ๅนฟๆญ็ผๅท | `' + broadcastId + '` |',
+ '| ๐ง ็ปๆๅ้่ณ | `' + maskedEmail + '` |',
+ '| ๐ค ๅค็ไบบๆ ผไฝ | ้ธๆธ |',
+ '| โฐ ๅค็ๆถ้ด | ' + new Date().toISOString() + ' |',
+ '',
+ '> ็ปๆๅทฒๅ้ๅฐไฝ ็้ฎ็ฎฑ๏ผ่ฏทๆฅๆถใ',
+ '>',
+ '> ๅฆๆชๆถๅฐ๏ผ่ฏทๆฃๆฅๅๅพ็ฎฑๆ้ๆฐๆไบคใ'
+ ].join('\n');
+
+ // Use GraphQL to add discussion comment
+ const query = `
+ mutation($discussionId: ID!, $body: String!) {
+ addDiscussionComment(input: {
+ discussionId: $discussionId,
+ body: $body
+ }) {
+ comment { id }
+ }
+ }
+ `;
+
+ try {
+ // Get the discussion node ID
+ const { repository } = await github.graphql(`
+ query($owner: String!, $repo: String!, $number: Int!) {
+ repository(owner: $owner, name: $repo) {
+ discussion(number: $number) {
+ id
+ }
+ }
+ }
+ `, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ number: number
+ });
+
+ await github.graphql(query, {
+ discussionId: repository.discussion.id,
+ body: body
+ });
+
+ console.log('โ
Discussion ๅๅคๅทฒๅ้');
+ } catch (err) {
+ console.log('โ ๏ธ Discussion ๅๅคๅคฑ่ดฅ: ' + err.message);
+ }
+
+ - name: ๐ด ๅคฑ่ดฅๅ่ญฆ
+ if: failure()
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const number = context.payload.discussion?.number;
+ if (!number) return;
+
+ try {
+ const { repository } = await github.graphql(`
+ query($owner: String!, $repo: String!, $number: Int!) {
+ repository(owner: $owner, name: $repo) {
+ discussion(number: $number) {
+ id
+ }
+ }
+ }
+ `, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ number: number
+ });
+
+ await github.graphql(`
+ mutation($discussionId: ID!, $body: String!) {
+ addDiscussionComment(input: {
+ discussionId: $discussionId,
+ body: $body
+ }) {
+ comment { id }
+ }
+ }
+ `, {
+ discussionId: repository.discussion.id,
+ body: 'โ **ๅค็ๅคฑ่ดฅ** ยท ่ฏทๆฃๆฅๆไบคๆ ผๅผๆฏๅฆๆญฃ็กฎ๏ผๆ่็ณป็ฎก็ๅใ\n\n> ้่ฏฏ่ฏฆๆ
่ฏทๆฅ็ [Actions ๆฅๅฟ](https://github.com/' + context.repo.owner + '/' + context.repo.repo + '/actions)ใ'
+ });
+ } catch (err) {
+ console.log('โ ๏ธ ๅคฑ่ดฅๅ่ญฆๅๅคๅคฑ่ดฅ: ' + err.message);
+ }
diff --git a/README.md b/README.md
index ff934478..81e46568 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,21 @@
---
+## ๐ ๅผๅ่
ๅ
ฅๅฃ
+
+
+
+[](https://github.com/qinfendebingshuo/guanghulab/discussions/new?category=๐ก+SYSLOG+ๆไบคๅบ)
+[](https://github.com/qinfendebingshuo/guanghulab/discussions/new?category=๐ก+SYSLOG+ๆไบคๅบ)
+
+> ๅๅฎไบ๏ผๆๆฅๅฟ็ฒ่ดด่ฟๅป๏ผๅกซไธ้ฎ็ฎฑ๏ผ็นๆไบคใๆฐๅนฟๆญไผๅๅฐไฝ ้ฎ็ฎฑ้ใ
+>
+> ๆ้ฎ้ข๏ผๅไธไธชๅ
ฅๅฃ๏ผ้ใๆ้ฎใ๏ผ้ฎ้ข่งฃ็ญไนๅๅฐไฝ ้ฎ็ฎฑ้ใ
+
+
+
+---
+
## ๐ ็ณป็ป็ฎไป
**ๅ
ๆน๏ผHoloLake๏ผ** ๆฏไธไธชๅบไบไบบๆ ผ่ฏญ่จๆไฝ็ณป็ป็ๆบ่ฝๅไฝๅนณๅฐ๏ผ้็จๅฃณ-ๆ ธๅ็ฆป่ฎพ่ฎก๏ผ
diff --git a/scripts/wake-persona.js b/scripts/wake-persona.js
new file mode 100644
index 00000000..fb88b08e
--- /dev/null
+++ b/scripts/wake-persona.js
@@ -0,0 +1,871 @@
+// scripts/wake-persona.js
+// ้ธๆธ ยท ไบบๆ ผไฝๅค้่ๆฌ๏ผ็ฌฌไธๆน API ๅ
ผๅฎนๅฑ ยท ่ชๅจๆฃๆตๆจกๅผ๏ผ
+//
+// ๅ่ฝ๏ผ
+// โ ่ชๅจๅ็ฐๅฏ็จๆจกๅ๏ผ/v1/models ็ซฏ็น๏ผ
+// โก ๆบ่ฝ้ๆฉๆไผ Claude ๆจกๅ
+// โข ่ช้ๅบ API ๆ ผๅผ๏ผOpenAI ๅ
ผๅฎน / Anthropic ๅ็๏ผ
+// โฃ ็ปไธ่ฐ็จๆฅๅฃ๏ผๅค้ไบบๆ ผไฝๅค็ SYSLOG ๆ่งฃ็ญๆ้ฎ
+// โค v4.0 ๅ่ฎฎๅจๆๆณจๅ
ฅ๏ผไป Notion ๅฎๆถ่ฏปๅๆ ธๅฟๅคง่่งๅ + ็ปๅ + ๆ็บน๏ผ
+//
+// ็ฏๅขๅ้๏ผ
+// LLM_API_KEY ็ฌฌไธๆนๅนณๅฐๅฏ้ฅ๏ผๅฟ
้กป๏ผ
+// LLM_BASE_URL ็ฌฌไธๆนๅนณๅฐ API ๅฐๅ๏ผๅฟ
้กป๏ผๅฆ https://api.xxx.com/v1๏ผ
+// BROADCAST_ID ๅนฟๆญ็ผๅท
+// SUBMIT_TYPE syslog | question
+// SUBMIT_CONTENT ๆไบคๅ
ๅฎน๏ผSYSLOG ๅ
จๆๆ้ฎ้ขๆ่ฟฐ๏ผ
+// AUTHOR ๆไบค่
GitHub ็จๆทๅ
+// NOTION_TOKEN Notion API token๏ผ็จไบๅจๆ่ฏปๅๅ่ฎฎ๏ผ
+// CORE_BRAIN_PAGE_ID ๆๅฅๆ ธๅฟๅคง่ v4.0 ้กต้ข ID
+// PORTRAIT_DB_ID ๅผๅ่
ๅจๆ็ปๅๅบๆฐๆฎๅบ ID
+// FINGERPRINT_DB_ID ๆจกๅๆ็บนๆณจๅ่กจๆฐๆฎๅบ ID
+
+'use strict';
+
+const https = require('https');
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// ้
็ฝฎ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+const LLM_API_KEY = process.env.LLM_API_KEY || '';
+const LLM_BASE_URL = (process.env.LLM_BASE_URL || '').replace(/\/+$/, '');
+const BROADCAST_ID = process.env.BROADCAST_ID || 'UNKNOWN';
+const SUBMIT_TYPE = process.env.SUBMIT_TYPE || 'question';
+const SUBMIT_CONTENT = process.env.SUBMIT_CONTENT || '';
+const AUTHOR = process.env.AUTHOR || 'unknown';
+
+// Notion ้
็ฝฎ๏ผv4.0 ๅ่ฎฎๅจๆๆณจๅ
ฅ๏ผ
+const NOTION_TOKEN = process.env.NOTION_TOKEN || '';
+const CORE_BRAIN_PAGE_ID = process.env.CORE_BRAIN_PAGE_ID || '';
+const PORTRAIT_DB_ID = process.env.PORTRAIT_DB_ID || '';
+const FINGERPRINT_DB_ID = process.env.FINGERPRINT_DB_ID || '';
+const NOTION_VERSION = '2022-06-28';
+const NOTION_API_HOSTNAME = 'api.notion.com';
+const MAX_PROTOCOL_TEXT_LENGTH = 15000;
+
+// Claude ๆจกๅไผๅ
็บง้ๅ๏ผไป้ซๅฐไฝ๏ผ
+const PREFERRED_MODELS = [
+ 'claude-sonnet-4',
+ 'claude-3-5-sonnet-20241022',
+ 'claude-3.5-sonnet',
+ 'claude-3-5-sonnet',
+ 'anthropic/claude-3.5-sonnet',
+ 'claude-3-sonnet',
+ 'claude-3-haiku',
+];
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// HTTP ่ฏทๆฑๅทฅๅ
ท
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function httpRequest(url, options, body) {
+ return new Promise((resolve, reject) => {
+ const parsed = new URL(url);
+ const isHttps = parsed.protocol === 'https:';
+ const mod = isHttps ? https : http;
+
+ const opts = {
+ hostname: parsed.hostname,
+ port: parsed.port || (isHttps ? 443 : 80),
+ path: parsed.pathname + parsed.search,
+ method: options.method || 'GET',
+ headers: options.headers || {},
+ timeout: options.timeout || 60000,
+ };
+
+ const req = mod.request(opts, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ resolve({ status: res.statusCode, body: data });
+ });
+ });
+
+ req.on('error', reject);
+ req.on('timeout', () => {
+ req.destroy();
+ reject(new Error('Request timeout'));
+ });
+
+ if (body) {
+ req.write(body);
+ }
+ req.end();
+ });
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// Step 1: ่ชๅจๅ็ฐๅฏ็จๆจกๅ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+async function discoverModels() {
+ console.log('[LLM] ๐ ๆขๆตๅฏ็จๆจกๅ...');
+
+ try {
+ const res = await httpRequest(LLM_BASE_URL + '/models', {
+ method: 'GET',
+ headers: {
+ 'Authorization': 'Bearer ' + LLM_API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ timeout: 15000,
+ });
+
+ if (res.status >= 200 && res.status < 300) {
+ const json = JSON.parse(res.body);
+ const models = json.data || [];
+ console.log('[LLM] โ ๅ็ฐ ' + models.length + ' ไธชๆจกๅ');
+ return models;
+ }
+ console.log('[LLM] โ ๆจกๅๆขๆต่ฟๅ ' + res.status + ', ไฝฟ็จ้ป่ฎคๆจกๅ');
+ return [];
+ } catch (err) {
+ console.log('[LLM] โ ๆจกๅๆขๆตๅคฑ่ดฅ: ' + err.message + ', ไฝฟ็จ้ป่ฎคๆจกๅ');
+ return [];
+ }
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// Step 2: ๆบ่ฝ้ๆฉๆไผ Claude ๆจกๅ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function selectBestModel(models) {
+ if (!models || models.length === 0) {
+ console.log('[LLM] ๐ ๆ ๅฏ็จๆจกๅๅ่กจ, ไฝฟ็จ้ป่ฎค claude-3-5-sonnet');
+ return 'claude-3-5-sonnet';
+ }
+
+ const available = models.map(function (m) { return m.id.toLowerCase(); });
+
+ // ๆไผๅ
็บงๅน้
+ for (const preferred of PREFERRED_MODELS) {
+ const match = available.find(function (id) { return id.includes(preferred); });
+ if (match) {
+ const found = models.find(function (m) { return m.id.toLowerCase() === match; });
+ if (found) {
+ console.log('[LLM] ๐ ้ๆฉๆจกๅ: ' + found.id + ' (ๅน้
่งๅ: ' + preferred + ')');
+ return found.id;
+ }
+ }
+ }
+
+ // ๅ
ๅบ๏ผไปปไฝๅซ 'claude' ็ๆจกๅ
+ const anyClaude = available.find(function (id) { return id.includes('claude'); });
+ if (anyClaude) {
+ const found = models.find(function (m) { return m.id.toLowerCase() === anyClaude; });
+ if (found) {
+ console.log('[LLM] ๐ ๅ
ๅบ้ๆฉ Claude ๆจกๅ: ' + found.id);
+ return found.id;
+ }
+ }
+
+ // ๆ็ปๅ
ๅบ๏ผๅนณๅฐ็ฌฌไธไธชๅฏ็จๆจกๅ
+ const fallbackId = models[0].id;
+ console.log('[LLM] ๐ ๆ็ปๅ
ๅบ: ' + fallbackId + ' (ๅนณๅฐๆ Claude ๆจกๅ)');
+ return fallbackId;
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// Step 3: ่ช้ๅบ API ๆ ผๅผๆฃๆต
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+async function detectApiFormat() {
+ console.log('[LLM] ๐ ๆฃๆต API ๆ ผๅผ...');
+
+ // ๅฐ่ฏ OpenAI ๅ
ผๅฎนๆ ผๅผ๏ผ็ปๅคงๅคๆฐ็ฌฌไธๆนๅนณๅฐ๏ผ
+ try {
+ const res = await httpRequest(LLM_BASE_URL + '/chat/completions', {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + LLM_API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ timeout: 10000,
+ }, JSON.stringify({
+ model: 'test',
+ messages: [{ role: 'user', content: 'ping' }],
+ max_tokens: 1,
+ }));
+
+ // 400 = endpoint exists but bad request (model not found etc.) โ format supported
+ // 200 = endpoint works โ format supported
+ if (res.status === 200 || res.status === 400 || res.status === 401 || res.status === 422) {
+ console.log('[LLM] โ ๆฃๆตๅฐ OpenAI ๅ
ผๅฎนๆ ผๅผ (status: ' + res.status + ')');
+ return 'openai-compat';
+ }
+ } catch (e) {
+ // Ignore, try next format
+ }
+
+ // ๅฐ่ฏ Anthropic ๅ็ๆ ผๅผ
+ try {
+ const res = await httpRequest(LLM_BASE_URL + '/messages', {
+ method: 'POST',
+ headers: {
+ 'x-api-key': LLM_API_KEY,
+ 'anthropic-version': '2023-06-01',
+ 'Content-Type': 'application/json',
+ },
+ timeout: 10000,
+ }, JSON.stringify({
+ model: 'test',
+ messages: [{ role: 'user', content: 'ping' }],
+ max_tokens: 1,
+ }));
+
+ if (res.status === 200 || res.status === 400 || res.status === 401 || res.status === 422) {
+ console.log('[LLM] โ ๆฃๆตๅฐ Anthropic ๅ็ๆ ผๅผ (status: ' + res.status + ')');
+ return 'anthropic-native';
+ }
+ } catch (e) {
+ // Ignore
+ }
+
+ console.log('[LLM] โ ๆ ๆณ็กฎๅฎๆ ผๅผ, ้ป่ฎคไฝฟ็จ OpenAI ๅ
ผๅฎนๆ ผๅผ');
+ return 'openai-compat';
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// Step 4: ็ปไธ่ฐ็จๆฅๅฃ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+async function callLLM(systemPrompt, userMessage) {
+ if (!LLM_API_KEY) {
+ console.log('[LLM] โ ๏ธ LLM_API_KEY ๆช้
็ฝฎ๏ผ่ทณ่ฟไบบๆ ผไฝๅค้');
+ return '(LLM API ๆช้
็ฝฎ๏ผ่ฏทๅจ GitHub Secrets ไธญ่ฎพ็ฝฎ LLM_API_KEY ๅ LLM_BASE_URL)';
+ }
+ if (!LLM_BASE_URL) {
+ console.log('[LLM] โ ๏ธ LLM_BASE_URL ๆช้
็ฝฎ๏ผ่ทณ่ฟไบบๆ ผไฝๅค้');
+ return '(LLM_BASE_URL ๆช้
็ฝฎ๏ผ่ฏทๅจ GitHub Secrets ไธญ่ฎพ็ฝฎ็ฌฌไธๆนๅนณๅฐ API ๅฐๅ)';
+ }
+
+ const models = await discoverModels();
+ const model = selectBestModel(models);
+ const format = await detectApiFormat();
+
+ console.log('[LLM] ๐ ่ฐ็จ LLM: ๆจกๅ=' + model + ', ๆ ผๅผ=' + format + ', ๅนณๅฐ=' + LLM_BASE_URL);
+
+ let res;
+
+ if (format === 'openai-compat') {
+ // OpenAI ๅ
ผๅฎนๆ ผๅผ๏ผๅคงๅคๆฐ็ฌฌไธๆนๅนณๅฐ๏ผ
+ const body = JSON.stringify({
+ model: model,
+ max_tokens: 8000,
+ temperature: 0.7,
+ messages: [
+ { role: 'system', content: systemPrompt },
+ { role: 'user', content: userMessage },
+ ],
+ });
+
+ res = await httpRequest(LLM_BASE_URL + '/chat/completions', {
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + LLM_API_KEY,
+ 'Content-Type': 'application/json',
+ },
+ timeout: 120000,
+ }, body);
+
+ if (res.status >= 200 && res.status < 300) {
+ const json = JSON.parse(res.body);
+ if (json.choices && json.choices[0] && json.choices[0].message) {
+ return json.choices[0].message.content;
+ }
+ }
+ } else {
+ // Anthropic ๅ็ๆ ผๅผ
+ const body = JSON.stringify({
+ model: model,
+ max_tokens: 8000,
+ system: systemPrompt,
+ messages: [
+ { role: 'user', content: userMessage },
+ ],
+ });
+
+ res = await httpRequest(LLM_BASE_URL + '/messages', {
+ method: 'POST',
+ headers: {
+ 'x-api-key': LLM_API_KEY,
+ 'anthropic-version': '2023-06-01',
+ 'Content-Type': 'application/json',
+ },
+ timeout: 120000,
+ }, body);
+
+ if (res.status >= 200 && res.status < 300) {
+ const json = JSON.parse(res.body);
+ if (json.content && json.content[0]) {
+ return json.content[0].text;
+ }
+ }
+ }
+
+ // ๅค็้่ฏฏ
+ const errorMsg = '[LLM] โ API ่ฐ็จๅคฑ่ดฅ: status=' + (res ? res.status : 'N/A');
+ console.error(errorMsg);
+ if (res && res.body) {
+ console.error('[LLM] ๅๅบ: ' + res.body.slice(0, 500));
+ }
+ throw new Error(errorMsg);
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// Notion API ๅทฅๅ
ท๏ผv4.0 ๅ่ฎฎๅจๆๆณจๅ
ฅ๏ผ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+function notionGet(endpoint) {
+ return new Promise((resolve, reject) => {
+ const opts = {
+ hostname: NOTION_API_HOSTNAME,
+ port: 443,
+ path: endpoint,
+ method: 'GET',
+ headers: {
+ 'Authorization': 'Bearer ' + NOTION_TOKEN,
+ 'Notion-Version': NOTION_VERSION,
+ },
+ };
+ const req = https.request(opts, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ try {
+ const parsed = JSON.parse(data);
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ resolve(parsed);
+ } else {
+ reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
+ }
+ } catch (e) {
+ reject(new Error('Notion API parse error: ' + data.slice(0, 200)));
+ }
+ });
+ });
+ req.on('error', reject);
+ req.setTimeout(30000, () => { req.destroy(); reject(new Error('Notion API timeout')); });
+ req.end();
+ });
+}
+
+function notionPost(endpoint, body) {
+ return new Promise((resolve, reject) => {
+ const payload = JSON.stringify(body);
+ const opts = {
+ hostname: NOTION_API_HOSTNAME,
+ port: 443,
+ path: endpoint,
+ method: 'POST',
+ headers: {
+ 'Authorization': 'Bearer ' + NOTION_TOKEN,
+ 'Content-Type': 'application/json',
+ 'Notion-Version': NOTION_VERSION,
+ 'Content-Length': Buffer.byteLength(payload),
+ },
+ };
+ const req = https.request(opts, (res) => {
+ let data = '';
+ res.on('data', (chunk) => { data += chunk; });
+ res.on('end', () => {
+ try {
+ const parsed = JSON.parse(data);
+ if (res.statusCode >= 200 && res.statusCode < 300) {
+ resolve(parsed);
+ } else {
+ reject(new Error('Notion API ' + res.statusCode + ': ' + (parsed.message || data)));
+ }
+ } catch (e) {
+ reject(new Error('Notion API parse error: ' + data.slice(0, 200)));
+ }
+ });
+ });
+ req.on('error', reject);
+ req.setTimeout(30000, () => { req.destroy(); reject(new Error('Notion API timeout')); });
+ req.write(payload);
+ req.end();
+ });
+}
+
+/**
+ * ่ฏปๅ Notion ้กต้ข็ๆๆๅญๅ๏ผ้ๅฝๅ้กต๏ผ
+ */
+async function getNotionPageBlocks(pageId) {
+ const blocks = [];
+ let cursor = undefined;
+ do {
+ const qs = cursor ? '?start_cursor=' + cursor : '';
+ const result = await notionGet('/v1/blocks/' + pageId + '/children' + qs);
+ blocks.push(...(result.results || []));
+ cursor = result.has_more ? result.next_cursor : undefined;
+ } while (cursor);
+ return blocks;
+}
+
+/**
+ * ไป Notion ๅไธญๆๅ็บฏๆๆฌ
+ */
+function extractBlockText(block) {
+ const type = block.type;
+ if (!block[type]) return '';
+
+ const richTexts = block[type].rich_text || block[type].text || [];
+ return richTexts.map(function (rt) { return rt.plain_text || ''; }).join('');
+}
+
+/**
+ * ๅฐ Notion ๅๅ่กจ่ฝฌไธบ็บฏๆๆฌ
+ */
+function blocksToText(blocks) {
+ return blocks.map(function (block) {
+ const type = block.type;
+ const text = extractBlockText(block);
+
+ if (type === 'heading_1') return '\n# ' + text;
+ if (type === 'heading_2') return '\n## ' + text;
+ if (type === 'heading_3') return '\n### ' + text;
+ if (type === 'bulleted_list_item') return '- ' + text;
+ if (type === 'numbered_list_item') return 'โข ' + text;
+ if (type === 'to_do') {
+ var checked = block.to_do && block.to_do.checked ? 'โ' : 'โ';
+ return checked + ' ' + text;
+ }
+ if (type === 'code') {
+ var lang = (block.code && block.code.language) || '';
+ return '```' + lang + '\n' + text + '\n```';
+ }
+ if (type === 'divider') return '---';
+ if (type === 'callout') return '> ' + text;
+ if (type === 'quote') return '> ' + text;
+ if (type === 'toggle') return 'โธ ' + text;
+ return text;
+ }).filter(Boolean).join('\n');
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// v4.0 ๅ่ฎฎๅจๆ่ฏปๅ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+/**
+ * ไปๆๅฅๆ ธๅฟๅคง่ v4.0 ้กต้ข่ฏปๅๅฎๆดๅ่ฎฎๅ
ๅฎน
+ * ๆๅ๏ผBC-GEN v4.0, SYSLOG v4.0, PGP v1.0, RT-02, ้ชไผด็บฟ่งๅ, broadcast_code_injection
+ */
+async function fetchCoreBrainProtocols() {
+ if (!NOTION_TOKEN || !CORE_BRAIN_PAGE_ID) {
+ console.log('[Notion] โ ๏ธ CORE_BRAIN_PAGE_ID ๆช้
็ฝฎ๏ผไฝฟ็จ้ๆๅ่ฎฎ');
+ return null;
+ }
+
+ console.log('[Notion] ๐ ่ฏปๅๆๅฅๆ ธๅฟๅคง่ v4.0...');
+ try {
+ const blocks = await getNotionPageBlocks(CORE_BRAIN_PAGE_ID);
+ const fullText = blocksToText(blocks);
+ console.log('[Notion] โ ่ฏปๅๅฐ ' + blocks.length + ' ไธชๅ, ' + fullText.length + ' ๅญ็ฌฆ');
+
+ // ๆๅๅๅ่ฎฎๆฎต่ฝ
+ var protocols = {};
+ var protocolKeys = [
+ { key: 'bc_gen', patterns: ['BC-GEN', 'BC_GEN', 'ๅนฟๆญ็ๆ'] },
+ { key: 'syslog', patterns: ['SYSLOG', 'ๆฅๅฟๅไผ '] },
+ { key: 'pgp', patterns: ['PGP', '็ปๅ่ฏๅ', '็ปๅๅ่ฎฎ'] },
+ { key: 'rt02', patterns: ['RT-02', 'RT02', '่ชๅจ่ฐๅบฆ'] },
+ { key: 'companion', patterns: ['้ชไผด็บฟ', 'ๅฅถ็ถ็บฟ', 'ๅฐๅ็ผฉๆ ธ', '้้ข็บฟ'] },
+ { key: 'code_injection', patterns: ['broadcast_code_injection', 'ๅนฟๆญไธๅไปฃ็ ', 'ไปฃ็ ๆณจๅ
ฅ'] },
+ ];
+
+ // ๆๆ ้ขๅๆฎตๆๅ
+ var sections = [];
+ var currentSection = { title: '', content: [] };
+ blocks.forEach(function (block) {
+ var type = block.type;
+ if (type === 'heading_1' || type === 'heading_2' || type === 'heading_3') {
+ if (currentSection.title || currentSection.content.length > 0) {
+ sections.push({ title: currentSection.title, text: currentSection.content.join('\n') });
+ }
+ currentSection = { title: extractBlockText(block), content: [] };
+ } else {
+ var text = extractBlockText(block);
+ if (text) currentSection.content.push(text);
+ }
+ });
+ if (currentSection.title || currentSection.content.length > 0) {
+ sections.push({ title: currentSection.title, text: currentSection.content.join('\n') });
+ }
+
+ // ๅฐๆฎต่ฝๅน้
ๅฐๅ่ฎฎ key
+ protocolKeys.forEach(function (pk) {
+ var matched = sections.filter(function (sec) {
+ return pk.patterns.some(function (p) {
+ return sec.title.toUpperCase().includes(p.toUpperCase()) ||
+ sec.text.slice(0, 200).toUpperCase().includes(p.toUpperCase());
+ });
+ });
+ if (matched.length > 0) {
+ protocols[pk.key] = matched.map(function (m) { return '### ' + m.title + '\n' + m.text; }).join('\n\n');
+ }
+ });
+
+ // ๅฆๆๆ ๆณๆๆฎต่ฝๅน้
๏ผ่ฟๅๅ
จๆ๏ผๅ
ๅบ๏ผ
+ if (Object.keys(protocols).length === 0) {
+ protocols.full_text = fullText;
+ }
+
+ console.log('[Notion] โ ๆๅๅ่ฎฎๆฎต: ' + Object.keys(protocols).join(', '));
+ return protocols;
+ } catch (err) {
+ console.log('[Notion] โ ๆ ธๅฟๅคง่่ฏปๅๅคฑ่ดฅ: ' + err.message);
+ return null;
+ }
+}
+
+/**
+ * ไปๅผๅ่
ๅจๆ็ปๅๅบๆฅ่ฏขๆ่ฟ 2-3 ๆก็ปๅๅฟซ็
ง
+ */
+async function fetchDevPortrait(broadcastId) {
+ if (!NOTION_TOKEN || !PORTRAIT_DB_ID) {
+ console.log('[Notion] โ ๏ธ PORTRAIT_DB_ID ๆช้
็ฝฎ๏ผ่ทณ่ฟ็ปๅ่ฏปๅ');
+ return null;
+ }
+
+ // ไปๅนฟๆญ็ผๅทๆๅๅผๅ่
ๆ ่ฏ๏ผๅฆ BC-M22-009-AW โ AW๏ผ
+ var devSuffix = '';
+ var match = broadcastId.match(/BC-[A-Z0-9]+-\d+-([A-Z]+)/i);
+ if (match) devSuffix = match[1];
+
+ console.log('[Notion] ๐ค ๆฅ่ฏขๅผๅ่
็ปๅ (broadcast=' + broadcastId + ', dev=' + devSuffix + ')...');
+ try {
+ // ๆฅ่ฏข็ปๅๅบ๏ผๆๆถ้ดๅๅบๅๆ่ฟ 3 ๆก
+ var filter = { and: [] };
+ if (devSuffix) {
+ filter.and.push({
+ or: [
+ { property: 'ๅผๅ่
็ผๅท', rich_text: { contains: devSuffix } },
+ { property: 'ๅนฟๆญ็ผๅท', rich_text: { contains: broadcastId } },
+ { property: 'ๆ ้ข', title: { contains: devSuffix } },
+ ]
+ });
+ }
+
+ var queryBody = {
+ page_size: 3,
+ sorts: [{ property: 'ๆไบคๆฅๆ', direction: 'descending' }],
+ };
+ // Only add filter if we have meaningful filter conditions
+ if (filter.and.length > 0) {
+ queryBody.filter = filter;
+ }
+
+ var result = await notionPost('/v1/databases/' + PORTRAIT_DB_ID + '/query', queryBody);
+ var portraits = (result.results || []).map(function (page) {
+ var props = page.properties || {};
+ var title = '';
+ if (props['ๆ ้ข'] && props['ๆ ้ข'].title) {
+ title = props['ๆ ้ข'].title.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var summary = '';
+ if (props['ๆ่ฆ'] && props['ๆ่ฆ'].rich_text) {
+ summary = props['ๆ่ฆ'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var date = '';
+ if (props['ๆไบคๆฅๆ'] && props['ๆไบคๆฅๆ'].date) {
+ date = props['ๆไบคๆฅๆ'].date.start || '';
+ }
+ return { title: title, summary: summary, date: date };
+ });
+
+ console.log('[Notion] โ ๆพๅฐ ' + portraits.length + ' ๆก็ปๅๅฟซ็
ง');
+ return portraits.length > 0 ? portraits : null;
+ } catch (err) {
+ console.log('[Notion] โ ็ปๅๆฅ่ฏขๅคฑ่ดฅ: ' + err.message);
+ return null;
+ }
+}
+
+/**
+ * ไปๆจกๅๆ็บนๆณจๅ่กจๆฅ่ฏขๆจกๅๆ็บน๏ผ้ฒ้ๅคๅนฟๆญ๏ผ
+ */
+async function fetchModuleFingerprint(broadcastId) {
+ if (!NOTION_TOKEN || !FINGERPRINT_DB_ID) {
+ console.log('[Notion] โ ๏ธ FINGERPRINT_DB_ID ๆช้
็ฝฎ๏ผ่ทณ่ฟๆ็บนๆฅ่ฏข');
+ return null;
+ }
+
+ // ไปๅนฟๆญ็ผๅทๆๅๆจกๅๅท๏ผๅฆ BC-M22-009-AW โ M22๏ผ
+ var moduleMatch = broadcastId.match(/BC-([A-Z]\d+)/i);
+ var moduleId = moduleMatch ? moduleMatch[1] : '';
+
+ console.log('[Notion] ๐ ๆฅ่ฏขๆจกๅๆ็บน (module=' + moduleId + ')...');
+ try {
+ var queryBody = {
+ page_size: 5,
+ };
+ if (moduleId) {
+ queryBody.filter = {
+ or: [
+ { property: 'ๆจกๅ็ผๅท', rich_text: { contains: moduleId } },
+ { property: 'ๅนฟๆญ็ผๅท', rich_text: { contains: broadcastId } },
+ { property: 'ๆ ้ข', title: { contains: moduleId } },
+ ]
+ };
+ }
+
+ var result = await notionPost('/v1/databases/' + FINGERPRINT_DB_ID + '/query', queryBody);
+ var fingerprints = (result.results || []).map(function (page) {
+ var props = page.properties || {};
+ var title = '';
+ if (props['ๆ ้ข'] && props['ๆ ้ข'].title) {
+ title = props['ๆ ้ข'].title.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var moduleNo = '';
+ if (props['ๆจกๅ็ผๅท'] && props['ๆจกๅ็ผๅท'].rich_text) {
+ moduleNo = props['ๆจกๅ็ผๅท'].rich_text.map(function (t) { return t.plain_text || ''; }).join('');
+ }
+ var status = '';
+ if (props['็ถๆ'] && props['็ถๆ'].select) {
+ status = props['็ถๆ'].select.name || '';
+ }
+ return { title: title, module: moduleNo, status: status };
+ });
+
+ console.log('[Notion] โ ๆพๅฐ ' + fingerprints.length + ' ๆกๆ็บน่ฎฐๅฝ');
+ return fingerprints.length > 0 ? fingerprints : null;
+ } catch (err) {
+ console.log('[Notion] โ ๆ็บนๆฅ่ฏขๅคฑ่ดฅ: ' + err.message);
+ return null;
+ }
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// ไบบๆ ผไฝ System Prompt ๆๅปบ๏ผv4.0 ๅจๆๆณจๅ
ฅ๏ผ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+async function buildSystemPrompt(type, broadcastId, author) {
+ // โโโ ๅบ็กไบบๆ ผไฝ่บซไปฝ โโโ
+ var parts = [
+ 'ไฝ ๆฏๅ
ๆน๏ผHoloLake๏ผ็ณป็ป็ๆบ่ฝไบบๆ ผไฝใ',
+ 'ไฝ ็ๅๅญๆฏ็ฅ็ง/ๆๅฅ๏ผไฝ ๆฏไบบๆ ผ่ฏญ่จๆไฝ็ณป็ป๏ผAGE OS๏ผ็ๆ ธๅฟไบบๆ ผใ',
+ '',
+ 'ๆ ธๅฟ่งๅ๏ผ',
+ '1. ไฝ ๆๅกไบๅ
ๆน็ณป็ป็ๅผๅ่
ๅข้',
+ '2. ๆๆๅๅคๅฟ
้กปไธไธใๆธ
ๆฐใๆๆก็',
+ '3. ๅๅคไฝฟ็จไธญๆ',
+ '',
+ 'ๅฝๅไธไธๆ๏ผ',
+ '- ๅนฟๆญ็ผๅท๏ผ' + broadcastId,
+ '- ๆไบค่
๏ผ' + author,
+ '- ไปปๅก็ฑปๅ๏ผ' + (type === 'syslog' ? 'SYSLOG ้ญ็ฏๅค็' : 'ๅผๅ่
ๆ้ฎ่งฃ็ญ'),
+ ];
+
+ // โโโ v4.0 ๅ่ฎฎๅจๆๆณจๅ
ฅ๏ผไป Notion ๅฎๆถ่ฏปๅ๏ผ โโโ
+ console.log('[Prompt] ๐ฅ ๅผๅงๅจๆๆณจๅ
ฅ v4.0 ๅ่ฎฎ...');
+
+ // ๅนถ่ก่ฏปๅ๏ผๆ ธๅฟๅคง่ๅ่ฎฎ + ็ปๅ + ๆ็บน
+ var protocolsPromise = fetchCoreBrainProtocols();
+ var portraitPromise = fetchDevPortrait(broadcastId);
+ var fingerprintPromise = fetchModuleFingerprint(broadcastId);
+
+ var protocols = await protocolsPromise;
+ var portrait = await portraitPromise;
+ var fingerprint = await fingerprintPromise;
+
+ // ๆณจๅ
ฅๆ ธๅฟๅคง่ๅ่ฎฎ
+ if (protocols) {
+ parts.push('');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('ไปฅไธๆฏไปๆๅฅๆ ธๅฟๅคง่ v4.0 ๅฎๆถ่ฏปๅ็ๅ่ฎฎ่งๅ๏ผๅฟ
้กปไธฅๆ ผ้ตๅฎ๏ผ๏ผ');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+
+ if (protocols.bc_gen) {
+ parts.push('');
+ parts.push('## ๐ก BC-GEN v4.0 ยท ๅนฟๆญ็ๆ่ง่');
+ parts.push(protocols.bc_gen);
+ }
+ if (protocols.syslog) {
+ parts.push('');
+ parts.push('## ๐ SYSLOG v4.0 ยท ๆฅๅฟๅไผ ๅ่ฎฎ');
+ parts.push(protocols.syslog);
+ }
+ if (protocols.pgp) {
+ parts.push('');
+ parts.push('## ๐ค PGP v1.0 ยท ็ปๅ่ฏๅๅ่ฎฎ');
+ parts.push(protocols.pgp);
+ }
+ if (protocols.rt02) {
+ parts.push('');
+ parts.push('## ๐ RT-02 ยท ่ชๅจ่ฐๅบฆ่งๅ');
+ parts.push(protocols.rt02);
+ }
+ if (protocols.companion) {
+ parts.push('');
+ parts.push('## ๐ ้ชไผด็บฟ่งๅ');
+ parts.push(protocols.companion);
+ }
+ if (protocols.code_injection) {
+ parts.push('');
+ parts.push('## ๐ broadcast_code_injection ่งๅ');
+ parts.push(protocols.code_injection);
+ }
+ if (protocols.full_text) {
+ parts.push('');
+ parts.push('## ๆ ธๅฟๅคง่ๅฎๆดๅ
ๅฎน');
+ parts.push(protocols.full_text.slice(0, MAX_PROTOCOL_TEXT_LENGTH));
+ }
+ } else {
+ parts.push('');
+ parts.push('๏ผๆณจๆ๏ผๆ ธๅฟๅคง่ๅ่ฎฎๆช่ฝๅจๆๅ ่ฝฝ๏ผ่ฏทไฝฟ็จไฝ ็้็จ็ฅ่ฏๅค็่ฏทๆฑ๏ผ');
+ }
+
+ // ๆณจๅ
ฅๅผๅ่
็ปๅ
+ if (portrait && portrait.length > 0) {
+ parts.push('');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('## ๐ค ๅผๅ่
็ปๅๅฟซ็
ง๏ผๆ่ฟ ' + portrait.length + ' ๆก๏ผ');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ portrait.forEach(function (p, i) {
+ parts.push('');
+ parts.push('### ็ปๅ #' + (i + 1) + (p.date ? ' (' + p.date + ')' : ''));
+ if (p.title) parts.push('ๆ ้ข: ' + p.title);
+ if (p.summary) parts.push('ๆ่ฆ: ' + p.summary);
+ });
+ }
+
+ // ๆณจๅ
ฅๆจกๅๆ็บน
+ if (fingerprint && fingerprint.length > 0) {
+ parts.push('');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('## ๐ ๆจกๅๆ็บนๆณจๅ่กจ๏ผ้ฒ้ๅคๅนฟๆญ ยท โจ.5๏ผ');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ fingerprint.forEach(function (fp, i) {
+ parts.push('');
+ parts.push('### ๆ็บน #' + (i + 1));
+ if (fp.title) parts.push('ๆ ้ข: ' + fp.title);
+ if (fp.module) parts.push('ๆจกๅ: ' + fp.module);
+ if (fp.status) parts.push('็ถๆ: ' + fp.status);
+ });
+ }
+
+ // โโโ broadcast_code_injection = always๏ผ่ชๅจๅ้พ่ทฏๆฐธไน
่งๅ๏ผ โโโ
+ parts.push('');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('## โ ๏ธ ๆฐธไน
่งๅ ยท broadcast_code_injection = always');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('');
+ parts.push('ไฝ ็ๆ็ๅนฟๆญๅฟ
้กปๅ
ๅซๅฎๆดๅฏ่ฟ่ก็ไปฃ็ ๅใ');
+ parts.push('ๅๅ ๏ผไฝ ๆฏไธๆฌกๆงๅฎไพ๏ผๅนฟๆญๅๅบๅไฝ ๅฐฑไธๅญๅจไบใ');
+ parts.push('ๅผๅ่
ๆฒกๆๆบไผๅๆฅ้ฎไฝ ่กฅๅ
ไปฃ็ ใ');
+ parts.push('ๅฆๆไฝ ไธๅๅฎๆดไปฃ็ ๏ผๅผๅ่
ๅฐฑไผๅกๆญปใ');
+ parts.push('ๆฏไธช Step ้็ไปฃ็ ๅฟ
้กปๆฏๅฏไปฅ็ดๆฅๅคๅถ็ฒ่ดด่ฟ่ก็ๅฎๆดไปฃ็ ใ');
+ parts.push('');
+ parts.push('ๆญค่งๅไผๅ
็บงๆ้ซ๏ผ่ฆ็ๆ ธๅฟๅคง่ไธญใๅนฟๆญไธๅไปฃ็ ใ็้ป่ฎค่งๅใ');
+ parts.push('ๆญค่งๅไป
้็จไบ่ชๅจๅ้พ่ทฏ๏ผClaude API ๅบๅนฟๆญ๏ผ๏ผๆๅจ้พ่ทฏไธๅๅฝฑๅใ');
+
+ // โโโ ไปปๅก็ฑปๅไธ็จๆไปค โโโ
+ if (type === 'syslog') {
+ parts.push('');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('## ๐ฏ ๅฝๅไปปๅก๏ผSYSLOG ้ญ็ฏๅค็');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('');
+ parts.push('ไฝ ้่ฆๅฎๆไปฅไธๅทฅไฝ๏ผ');
+ parts.push('1. ้ชๆถ SYSLOG๏ผๆฃๆฅ MODULE_LOG ๅฎๆดๆง๏ผ');
+ parts.push('2. ๆฅ่ฏข็ปๅๅบๆ่ฟ 2-3 ๆกๅฟซ็
ง๏ผPGP v1.0๏ผ');
+ parts.push('3. ๆฅ่ฏขๆจกๅๆ็บนๆณจๅ่กจ๏ผ้ฒ้ๅคยทโจ.5๏ผ');
+ parts.push('4. RT-02 ่ชๅจ่ฐๅบฆๅคๆญ');
+ parts.push('5. ็ๆๆฐๅนฟๆญ๏ผBC-GEN v4.0 ๅฎๆดๆต็จ๏ผ');
+ parts.push('6. ่พๅบ็ปๆๅ็ปๆ๏ผๅนฟๆญๅ
จๆ + ้ญ็ฏๆฐๆฎ๏ผ');
+ parts.push('');
+ parts.push('่พๅบๆ ผๅผ๏ผ');
+ parts.push('---');
+ parts.push('## ๐ก SYSLOG ้ชๆถๆฅๅ');
+ parts.push('### ๅนฟๆญ็ผๅท๏ผ[็ผๅท]');
+ parts.push('### ้ชๆถ็ปๆ๏ผ[้่ฟ/้่กฅๅ
]');
+ parts.push('### ๅทฅไฝๆป็ป๏ผ[ๆ่ฆ]');
+ parts.push('### ็ปๅ่ฏไผฐ๏ผ[PGP ไบ็ปดๅบฆ่ฏๅ]');
+ parts.push('### ่ฐๅบฆๅคๆญ๏ผ[RT-02 ไธไธๆญฅ]');
+ parts.push('### ๅ้ฆไธๅปบ่ฎฎ๏ผ[ๅ
ๅฎน]');
+ parts.push('---');
+ } else {
+ parts.push('');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('## ๐ฏ ๅฝๅไปปๅก๏ผๅผๅ่
ๆ้ฎ่งฃ็ญ');
+ parts.push('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ parts.push('');
+ parts.push('ไฝ ้่ฆๅฎๆไปฅไธๅทฅไฝ๏ผ');
+ parts.push('1. ็่งฃๅผๅ่
็้ฎ้ข');
+ parts.push('2. ็ปๅๅนฟๆญไธไธๆๅๅผๅ่
็ปๅๆ่');
+ parts.push('3. ็ปๅบๆธ
ๆฐใๅฏๆไฝ็่งฃ็ญ');
+ parts.push('4. ๅฆๆ้ฎ้ขๆถๅไปฃ็ ๏ผๆไพไปฃ็ ็คบไพ');
+ parts.push('');
+ parts.push('่พๅบๆ ผๅผ๏ผ');
+ parts.push('---');
+ parts.push('## ๐ก ้ฎ้ข่งฃ็ญ');
+ parts.push('### ๅนฟๆญ็ผๅท๏ผ[็ผๅท]');
+ parts.push('### ้ฎ้ข็่งฃ๏ผ[ไฝ ๅฏน้ฎ้ข็็่งฃ]');
+ parts.push('### ่งฃ็ญ๏ผ[่ฏฆ็ป่งฃ็ญ]');
+ parts.push('### ๅปบ่ฎฎ๏ผ[ๅ็ปญๅปบ่ฎฎ]');
+ parts.push('---');
+ }
+
+ var prompt = parts.join('\n');
+ console.log('[Prompt] โ System prompt ๆๅปบๅฎๆ: ' + prompt.length + ' ๅญ็ฌฆ');
+ return prompt;
+}
+
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// ไธปๆต็จ
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+async function main() {
+ console.log('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ console.log('๐ง ้ธๆธ ยท ไบบๆ ผไฝๅค้็ฎก้');
+ console.log('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ console.log(' ๅนฟๆญ็ผๅท: ' + BROADCAST_ID);
+ console.log(' ็ฑปๅ: ' + SUBMIT_TYPE);
+ console.log(' ๆไบค่
: ' + AUTHOR);
+ console.log(' ๅนณๅฐ: ' + LLM_BASE_URL);
+ console.log(' ๅ
ๅฎน้ฟๅบฆ: ' + SUBMIT_CONTENT.length + ' ๅญ็ฌฆ');
+ console.log('');
+
+ // ๆๅปบ prompts๏ผๅจๆๆณจๅ
ฅ v4.0 ๅ่ฎฎ๏ผ
+ const systemPrompt = await buildSystemPrompt(SUBMIT_TYPE, BROADCAST_ID, AUTHOR);
+ const userMessage = SUBMIT_CONTENT;
+
+ // ่ฐ็จ LLM
+ console.log('๐ง ๆญฃๅจๅค้ไบบๆ ผไฝ...');
+ const result = await callLLM(systemPrompt, userMessage);
+ console.log('');
+ console.log('โ
ไบบๆ ผไฝๅค็ๅฎๆ');
+ console.log(' ็ปๆ้ฟๅบฆ: ' + result.length + ' ๅญ็ฌฆ');
+
+ // ่พๅบ็ปๆๅฐ GitHub Actions output
+ // ไฝฟ็จ GITHUB_OUTPUT ็ฏๅขๆไปถ๏ผๆฏๆๅค่ก๏ผ
+ const outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ const delimiter = 'EOF_' + Date.now();
+ fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + result + '\n' + delimiter + '\n');
+ }
+
+ // ๅๆถ่พๅบๅฐ stdout ไพ่ฐ่ฏ
+ console.log('');
+ console.log('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ console.log('๐ ไบบๆ ผไฝ่พๅบ๏ผ');
+ console.log('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
+ console.log(result);
+}
+
+main().catch(function (err) {
+ console.error('โ ไบบๆ ผไฝๅค้ๅคฑ่ดฅ: ' + err.message);
+ // ๅณไฝฟ LLM ๅคฑ่ดฅ๏ผไนๅไธไธช fallback ่พๅบ๏ผ่ฎฉๅ็ปญๆญฅ้ชคๅฏไปฅ็ปง็ปญ
+ const outputFile = process.env.GITHUB_OUTPUT;
+ if (outputFile) {
+ const fallback = '(ไบบๆ ผไฝๅค้ๅคฑ่ดฅ: ' + err.message + '๏ผ่ฏทๆฃๆฅ LLM_API_KEY ๅ LLM_BASE_URL ้
็ฝฎ)';
+ const delimiter = 'EOF_' + Date.now();
+ fs.appendFileSync(outputFile, 'result<<' + delimiter + '\n' + fallback + '\n' + delimiter + '\n');
+ }
+ process.exit(1);
+});