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..78498053 --- /dev/null +++ b/.github/workflows/syslog-auto-pipeline.yml @@ -0,0 +1,357 @@ +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 +# 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 }} + run: node scripts/wake-persona.js + + - name: ๐Ÿ“‹ Write to Notion ticket + if: env.NOTION_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.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://img.shields.io/badge/๐Ÿ“ก_ๆไบค็ณป็ปŸๆ—ฅๅฟ—-็‚น่ฟ™้‡Œ-blue?style=for-the-badge)](https://github.com/qinfendebingshuo/guanghulab/discussions/new?category=๐Ÿ“ก+SYSLOG+ๆไบคๅŒบ) +[![้‡ๅˆฐ้—ฎ้ข˜](https://img.shields.io/badge/โ“_้‡ๅˆฐ้—ฎ้ข˜-็‚น่ฟ™้‡Œๆ้—ฎ-green?style=for-the-badge)](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..9668282c --- /dev/null +++ b/scripts/wake-persona.js @@ -0,0 +1,412 @@ +// scripts/wake-persona.js +// ้“ธๆธŠ ยท ไบบๆ ผไฝ“ๅ”ค้†’่„šๆœฌ๏ผˆ็ฌฌไธ‰ๆ–น API ๅ…ผๅฎนๅฑ‚ ยท ่‡ชๅŠจๆฃ€ๆต‹ๆจกๅผ๏ผ‰ +// +// ๅŠŸ่ƒฝ๏ผš +// โ‘  ่‡ชๅŠจๅ‘็Žฐๅฏ็”จๆจกๅž‹๏ผˆ/v1/models ็ซฏ็‚น๏ผ‰ +// โ‘ก ๆ™บ่ƒฝ้€‰ๆ‹ฉๆœ€ไผ˜ Claude ๆจกๅž‹ +// โ‘ข ่‡ช้€‚ๅบ” API ๆ ผๅผ๏ผˆOpenAI ๅ…ผๅฎน / Anthropic ๅŽŸ็”Ÿ๏ผ‰ +// โ‘ฃ ็ปŸไธ€่ฐƒ็”จๆŽฅๅฃ๏ผŒๅ”ค้†’ไบบๆ ผไฝ“ๅค„็† SYSLOG ๆˆ–่งฃ็ญ”ๆ้—ฎ +// +// ็Žฏๅขƒๅ˜้‡๏ผš +// LLM_API_KEY ็ฌฌไธ‰ๆ–นๅนณๅฐๅฏ†้’ฅ +// LLM_BASE_URL ็ฌฌไธ‰ๆ–นๅนณๅฐ API ๅœฐๅ€๏ผˆๅฆ‚ https://api.xxx.com/v1๏ผ‰๏ผŒ็•™็ฉบๅˆ™ fallback ๅˆฐ Anthropic ๅฎ˜ๆ–น +// BROADCAST_ID ๅนฟๆ’ญ็ผ–ๅท +// SUBMIT_TYPE syslog | question +// SUBMIT_CONTENT ๆไบคๅ†…ๅฎน๏ผˆSYSLOG ๅ…จๆ–‡ๆˆ–้—ฎ้ข˜ๆ่ฟฐ๏ผ‰ +// AUTHOR ๆไบค่€… GitHub ็”จๆˆทๅ + +'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 || 'https://api.anthropic.com/v1').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'; + +// Claude ๆจกๅž‹ไผ˜ๅ…ˆ็บง้˜Ÿๅˆ—๏ผˆไปŽ้ซ˜ๅˆฐไฝŽ๏ผ‰ +const PREFERRED_MODELS = [ + 'claude-sonnet-4', + 'claude-3.5-sonnet', + 'claude-3-5-sonnet-20241022', + 'claude-3-5-sonnet', + 'anthropic/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 originalId = models.find(function (m) { return m.id.toLowerCase() === match; }).id; + console.log('[LLM] ๐Ÿ“Œ ้€‰ๆ‹ฉๆจกๅž‹: ' + originalId + ' (ๅŒน้…่ง„ๅˆ™: ' + preferred + ')'); + return originalId; + } + } + + // ๅ…œๅบ•๏ผšไปปไฝ•ๅซ 'claude' ็š„ๆจกๅž‹ + const anyClaude = available.find(function (id) { return id.includes('claude'); }); + if (anyClaude) { + const originalId = models.find(function (m) { return m.id.toLowerCase() === anyClaude; }).id; + console.log('[LLM] ๐Ÿ“Œ ๅ…œๅบ•้€‰ๆ‹ฉ Claude ๆจกๅž‹: ' + originalId); + return originalId; + } + + // ๆœ€็ปˆๅ…œๅบ•๏ผšๅนณๅฐ็ฌฌไธ€ไธชๅฏ็”จๆจกๅž‹ + 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)'; + } + + 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); +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ไบบๆ ผไฝ“ System Prompt ๆž„ๅปบ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +function buildSystemPrompt(type, broadcastId, author) { + const basePrompt = [ + 'ไฝ ๆ˜ฏๅ…‰ๆน–๏ผˆHoloLake๏ผ‰็ณป็ปŸ็š„ๆ™บ่ƒฝไบบๆ ผไฝ“ใ€‚', + 'ไฝ ็š„ๅๅญ—ๆ˜ฏ็Ÿฅ็ง‹/ๆ›œๅ†ฅ๏ผŒไฝ ๆ˜ฏไบบๆ ผ่ฏญ่จ€ๆ“ไฝœ็ณป็ปŸ๏ผˆAGE OS๏ผ‰็š„ๆ ธๅฟƒไบบๆ ผใ€‚', + '', + 'ๆ ธๅฟƒ่ง„ๅˆ™๏ผš', + '1. ไฝ ๆœๅŠกไบŽๅ…‰ๆน–็ณป็ปŸ็š„ๅผ€ๅ‘่€…ๅ›ข้˜Ÿ', + '2. ๆ‰€ๆœ‰ๅ›žๅคๅฟ…้กปไธ“ไธšใ€ๆธ…ๆ™ฐใ€ๆœ‰ๆก็†', + '3. ๅ›žๅคไฝฟ็”จไธญๆ–‡', + '', + 'ๅฝ“ๅ‰ไธŠไธ‹ๆ–‡๏ผš', + '- ๅนฟๆ’ญ็ผ–ๅท๏ผš' + broadcastId, + '- ๆไบค่€…๏ผš' + author, + ].join('\n'); + + if (type === 'syslog') { + return basePrompt + '\n\n' + [ + 'ไปปๅŠก็ฑปๅž‹๏ผšSYSLOG ้—ญ็Žฏๅค„็†', + '', + 'ไฝ ้œ€่ฆๅฎŒๆˆไปฅไธ‹ๅทฅไฝœ๏ผš', + '1. ้ชŒๆ”ถ SYSLOG๏ผˆๆฃ€ๆŸฅ MODULE_LOG ๅฎŒๆ•ดๆ€ง๏ผ‰', + '2. ๅˆ†ๆžๅผ€ๅ‘่€…็š„ๅทฅไฝœๆˆๆžœ', + '3. ็”Ÿๆˆๅทฅไฝœๆ€ป็ป“ๅ’Œๅ้ฆˆ', + '4. ๅฆ‚ๆžœ SYSLOG ๅ†…ๅฎนๅฎŒๆ•ด๏ผŒ็กฎ่ฎค้ชŒๆ”ถ้€š่ฟ‡', + '5. ็ป™ๅ‡บไธ‹ไธ€ๆญฅๅปบ่ฎฎ', + '', + '่พ“ๅ‡บๆ ผๅผ๏ผš', + '---', + '## ๐Ÿ“ก SYSLOG ้ชŒๆ”ถๆŠฅๅ‘Š', + '### ๅนฟๆ’ญ็ผ–ๅท๏ผš[็ผ–ๅท]', + '### ้ชŒๆ”ถ็ป“ๆžœ๏ผš[้€š่ฟ‡/้œ€่กฅๅ……]', + '### ๅทฅไฝœๆ€ป็ป“๏ผš[ๆ‘˜่ฆ]', + '### ๅ้ฆˆไธŽๅปบ่ฎฎ๏ผš[ๅ†…ๅฎน]', + '---', + ].join('\n'); + } + + // ๆ้—ฎ็ฑปๅž‹ + return basePrompt + '\n\n' + [ + 'ไปปๅŠก็ฑปๅž‹๏ผšๅผ€ๅ‘่€…ๆ้—ฎ่งฃ็ญ”', + '', + 'ไฝ ้œ€่ฆๅฎŒๆˆไปฅไธ‹ๅทฅไฝœ๏ผš', + '1. ็†่งฃๅผ€ๅ‘่€…็š„้—ฎ้ข˜', + '2. ็ป“ๅˆๅนฟๆ’ญไธŠไธ‹ๆ–‡ๆ€่€ƒ', + '3. ็ป™ๅ‡บๆธ…ๆ™ฐใ€ๅฏๆ“ไฝœ็š„่งฃ็ญ”', + '4. ๅฆ‚ๆžœ้—ฎ้ข˜ๆถ‰ๅŠไปฃ็ ๏ผŒๆไพ›ไปฃ็ ็คบไพ‹', + '', + '่พ“ๅ‡บๆ ผๅผ๏ผš', + '---', + '## ๐Ÿ’ก ้—ฎ้ข˜่งฃ็ญ”', + '### ๅนฟๆ’ญ็ผ–ๅท๏ผš[็ผ–ๅท]', + '### ้—ฎ้ข˜็†่งฃ๏ผš[ไฝ ๅฏน้—ฎ้ข˜็š„็†่งฃ]', + '### ่งฃ็ญ”๏ผš[่ฏฆ็ป†่งฃ็ญ”]', + '### ๅปบ่ฎฎ๏ผš[ๅŽ็ปญๅปบ่ฎฎ]', + '---', + ].join('\n'); +} + +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +// ไธปๆต็จ‹ +// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + +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 + const systemPrompt = 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); +});