
Skill To Linebot
- 24 installs
- 4 repo stars
- Updated January 22, 2026
- wolke/skill-to-linebot
Helps with ai & agent building tasks.
About
skill-to-linebot is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- skill-to-linebot
- AI & Agent Building
- AI-coding skill
Skill To Linebot by the numbers
- 24 all-time installs (skills.sh)
- Ranked #9,912 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wolke/skill-to-linebot --skill skill-to-linebotAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24 |
|---|---|
| repo stars | ★ 4 |
| Last updated | January 22, 2026 |
| Repository | wolke/skill-to-linebot ↗ |
What it does
Helps with ai & agent building tasks.
Files
Skill to LINE Bot 轉換技能
將任意 AI Skill 轉換成可部署的 LINE Bot 服務。
---
使用時機
當用戶提出以下請求時啟用此技能:
- 「幫我把 XXX skill 變成 LINE Bot」
- 「將這個 skill 部署成 LINE Bot」
- 「我想讓這個 skill 能在 LINE 上使用」
- 「create a LINE Bot from this skill」
---
核心功能
1. Skill 分析 — 解析目標 skill 的結構、功能與用途 2. 程式碼生成 — 生成完整的 Google Apps Script 專案 3. 部署指南 — 產生詳細的部署步驟說明
---
工作流程
Step 1: 確認目標 Skill
詢問或確認用戶想要轉換的 skill:
請告訴我你想轉換的 skill 路徑,例如:
- .agent/skills/meihua-yishu
- .agent/skills/bazi
或者直接告訴我 skill 的名稱,我會幫你找到它。Step 2: 分析 Skill 結構
讀取並分析目標 skill 的以下內容:
2.1 必讀檔案
SKILL.md— 提取 name、description、核心功能、使用流程
2.2 選讀檔案
scripts/*.py— 識別計算邏輯(如有)references/*.md— 識別知識庫內容(如有)ETHICS.md— 提取倫理規範(如有)
2.3 分析要點
從 SKILL.md 中提取: 1. Skill 名稱 — 用於 Config.BOT_NAME 2. 功能描述 — 用於生成歡迎訊息 3. 核心功能 — 用於設計對話邏輯 4. 輸入類型 — 用戶可能輸入的資料類型(文字、數字、時間等) 5. 輸出格式 — 回應的內容結構
Step 3: 生成 GAS 專案
在用戶指定的目錄(或預設為 {skill-name}-line-bot/)建立以下檔案:
3.1 直接複製的模板(無需修改)
從 templates/ 目錄複製以下檔案:
| 模板檔案 | 目標檔案 |
|---|---|
appsscript.json | appsscript.json |
LineBot.gs.template | LineBot.gs |
Database.gs.template | Database.gs |
3.2 需要填充的模板
Config.gs
替換以下佔位符:
{{SKILL_NAME}}→ Skill 技術名稱(如meihua-yishu){{SKILL_DISPLAY_NAME}}→ Skill 顯示名稱(如梅花易數占卜){{WELCOME_MESSAGE}}→ 歡迎訊息
Main.gs
直接複製 Main.gs.template,無需修改。
OpenAI.gs
直接複製 OpenAI.gs.template,無需修改。
SkillCore.gs(最重要!)
這是需要根據 Skill 內容客製化的核心檔案。
替換以下佔位符:
`{{SYSTEM_PROMPT}}`
根據 Skill 的 SKILL.md 內容生成系統提示詞,應包含:
- Skill 的角色定義
- 核心功能說明
- 回應格式要求
- 注意事項與限制
範例(以梅花易數為例):
SYSTEM_PROMPT: `你是一位專業的梅花易數占卜師。
【核心功能】
- 時間起卦:根據當前時間進行占卜
- 數字起卦:根據用戶提供的數字進行占卜
- 卦象解讀:分析體用關係並提供建議
【回應要求】
1. 使用繁體中文回答
2. 先說明卦象,再給出解讀
3. 提供具體可行的建議
4. 語氣溫和、正面
【注意事項】
- 卦象僅供參考,不代表絕對結果
- 不預測具體死亡或極端不幸
- 鼓勵用戶獨立思考`,`{{PROCESS_MESSAGE_LOGIC}}`
根據 Skill 的功能生成處理邏輯。常見模式:
模式 A:純 AI 對話型
// 直接呼叫 OpenAI
const response = OpenAI.chat(userMessage, this.SYSTEM_PROMPT);
return response;模式 B:帶有計算邏輯型
// 先處理輸入,再呼叫 AI
const processedInput = this.preprocessInput(userMessage);
const enhancedPrompt = this.SYSTEM_PROMPT + '\n\n【用戶輸入分析】\n' + processedInput;
const response = OpenAI.chat(userMessage, enhancedPrompt);
return response;模式 C:使用 Vector Store 型
// 使用 Responses API 搜尋知識庫
const response = OpenAI.responsesWithSearch(userMessage, this.SYSTEM_PROMPT);
return response;`{{HELPER_FUNCTIONS}}`
根據需要添加輔助函數,例如:
- 輸入預處理
- 數字提取
- 時間處理
- 格式化輸出
Step 4: 生成部署指南
在專案目錄中建立 README.md,包含:
# {Skill 名稱} LINE Bot 部署指南
## 📋 快速開始
### 1. 建立 Google Sheets
1. 開啟 [Google Sheets](https://sheets.google.com)
2. 建立新試算表,命名為「{Skill 名稱} 資料庫」
3. 複製試算表 ID(網址中 `/d/` 和 `/edit` 之間的字串)
### 2. 建立 Google Apps Script 專案
1. 開啟 [Google Apps Script](https://script.google.com)
2. 點擊「新專案」
3. 將專案命名為「{Skill 名稱} LINE Bot」
4. 將以下檔案內容複製到 GAS 專案中:
- `Config.gs`
- `Main.gs`
- `LineBot.gs`
- `Database.gs`
- `OpenAI.gs`
- `SkillCore.gs`
### 3. 設定 Script Properties
在 GAS 中點擊 ⚙️「專案設定」 > 「指令碼屬性」,新增:
| 屬性名稱 | 說明 |
|----------|------|
| `LINE_CHANNEL_SECRET` | LINE Messaging API Channel Secret |
| `LINE_CHANNEL_ACCESS_TOKEN` | LINE Messaging API Access Token |
| `OPENAI_API_KEY` | OpenAI API Key |
| `SPREADSHEET_ID` | Google Sheets 試算表 ID |
### 4. 部署為網頁應用程式
1. 點擊「部署」→「新增部署作業」
2. 選擇類型:「網頁應用程式」
3. 設定:
- 執行身分:我
- 存取權限:任何人
4. 點擊「部署」
5. 複製產生的 **網頁應用程式網址**
### 5. 設定 LINE Developers
1. 開啟 [LINE Developers Console](https://developers.line.biz/console/)
2. 建立 **Messaging API Channel**
3. 在「Messaging API」設定中:
- 將 GAS 網址貼到 **Webhook URL**
- 啟用 **Use webhook**
- 關閉 **Auto-reply messages**
4. 取得 **Channel Secret** 和 **Channel Access Token**
## 🧪 測試
設定 `TEST_USER_ID` 後,在 GAS 中執行:
- `testLineSend()` — 測試 LINE 連接
- `testSkillCore()` — 測試功能邏輯
## 📱 使用
掃描 LINE 官方帳號 QR Code 加為好友,即可開始使用!Step 5: 產生輸出摘要
完成後,向用戶報告:
✅ LINE Bot 專案已生成!
📁 專案位置:{output_directory}
📄 生成的檔案:
- appsscript.json — GAS 設定
- Config.gs — 設定管理
- LineBot.gs — LINE API 封裝
- Database.gs — 資料庫操作
- OpenAI.gs — AI 整合
- Main.gs — Webhook 入口
- SkillCore.gs — {Skill 名稱} 核心邏輯
- README.md — 部署指南
📖 下一步:
1. 閱讀 README.md 中的部署指南
2. 建立 Google Sheets 和 GAS 專案
3. 設定 LINE Developers
4. 部署並測試
需要我幫你升級到 Flex Message 版本嗎?---
進階功能(Phase 2)
Flex Message 升級
當用戶要求升級 Flex Message 時:
1. 生成 LineMessages.gs,包含:
- 歡迎訊息 Flex 模板
- 結果顯示 Flex 模板
- 按鈕選單模板
2. 修改 Main.gs 中的回覆邏輯
⚠️ 注意:Flex Message 在 GAS 中除錯困難,建議先確保純文字版本運作正常後再升級。
Vector Store 整合
如果 Skill 有 references/ 目錄:
1. 建議用戶使用 scripts/upload-skills.js 上傳知識庫到 OpenAI Vector Store 2. 取得 OPENAI_VECTOR_STORE_ID 並填入 Script Properties 3. SkillCore 將自動使用 Responses API 進行 RAG
---
模板位置
所有模板檔案位於:
.agent/skills/skill-to-linebot/templates/
├── appsscript.json
├── Config.gs.template
├── LineBot.gs.template
├── Database.gs.template
├── OpenAI.gs.template
├── Main.gs.template
└── SkillCore.gs.template---
常見問題
Q: Webhook 返回 404
A: 確認已部署為「網頁應用程式」,而非「API 執行檔」。
Q: LINE 沒有回應
A: 檢查 Script Properties 中的 LINE_CHANNEL_ACCESS_TOKEN 是否正確。
Q: OpenAI 錯誤
A: 確認 OPENAI_API_KEY 正確且有足夠額度。
Q: 如何除錯?
A: 在 GAS 中點擊「執行項目」>「執行記錄」查看 Logger 輸出。
---
版本資訊
- 版本:1.0
- 更新日期:2026年1月
- 特點:
- 純文字優先,穩定易除錯
- 模組化設計,通用模組無需修改
- 支援 OpenAI Chat Completions 和 Responses API
- 簡化的 Google Sheets 資料庫操作
Skill to LINE Bot 轉換工具
!Skill to LINE Bot
將任意 AI Skill 轉換成可部署的 LINE Bot 服務。
✨ 特點
- 純文字優先 — 穩定易除錯,避免 Flex Message 複雜性
- 模組化設計 — 通用模組無需修改,只需填充業務邏輯
- 零成本部署 — GAS + Google Sheets 完全免費
🚀 使用方法
在對話中說:
幫我把 meihua-yishu skill 變成 LINE BotAI 將會: 1. 分析 skill 結構 2. 生成完整 GAS 專案 3. 提供部署指南
📁 結構
skill-to-linebot/
├── SKILL.md # 主要指令文件
├── README.md # 本文件
└── templates/ # GAS 程式碼模板
├── appsscript.json
├── Config.gs.template
├── LineBot.gs.template
├── Database.gs.template
├── OpenAI.gs.template
├── Main.gs.template
└── SkillCore.gs.template📦 生成的專案
轉換後將產生以下檔案:
| 檔案 | 說明 |
|---|---|
Config.gs | 設定管理 |
Main.gs | Webhook 入口 |
LineBot.gs | LINE API 封裝 |
Database.gs | Google Sheets 操作 |
OpenAI.gs | AI 整合 |
SkillCore.gs | Skill 核心邏輯 |
README.md | 部署指南 |
🔧 技術棧
- 後端: Google Apps Script
- 資料庫: Google Sheets
- AI: OpenAI (gpt-4o)
- 訊息: LINE Messaging API
📖 版本
- v1.0 — 純文字版本,支援基本對話功能
📜 授權
MIT License
{
"timeZone": "Asia/Taipei",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"webapp": {
"executeAs": "USER_DEPLOYING",
"access": "ANYONE_ANONYMOUS"
}
}/**
* {{SKILL_NAME}} LINE Bot - 設定檔
*
* 請將以下設定填入 Script Properties:
* File > Project properties > Script properties
*/
const Config = {
// LINE Bot
LINE_CHANNEL_SECRET: PropertiesService.getScriptProperties().getProperty('LINE_CHANNEL_SECRET') || '',
LINE_CHANNEL_ACCESS_TOKEN: PropertiesService.getScriptProperties().getProperty('LINE_CHANNEL_ACCESS_TOKEN') || '',
// OpenAI
OPENAI_API_KEY: PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY') || '',
OPENAI_MODEL: 'gpt-4o',
// Google Sheets
SPREADSHEET_ID: PropertiesService.getScriptProperties().getProperty('SPREADSHEET_ID') || '',
// 服務設定
BOT_NAME: '{{SKILL_DISPLAY_NAME}}',
WELCOME_MESSAGE: '{{WELCOME_MESSAGE}}',
// 可選:Vector Store(用於 RAG)
OPENAI_VECTOR_STORE_ID: PropertiesService.getScriptProperties().getProperty('OPENAI_VECTOR_STORE_ID') || ''
};
/**
* Google Sheets 資料庫操作
*
* 此模組為通用模組,提供用戶管理和使用記錄功能
*/
const Database = {
/**
* 取得試算表
*/
getSpreadsheet() {
return SpreadsheetApp.openById(Config.SPREADSHEET_ID);
},
/**
* 取得工作表(不存在則自動建立)
*/
getSheet(name) {
const ss = this.getSpreadsheet();
let sheet = ss.getSheetByName(name);
if (!sheet) {
sheet = ss.insertSheet(name);
this.initSheet(sheet, name);
}
return sheet;
},
/**
* 初始化工作表欄位
*/
initSheet(sheet, name) {
const headers = {
'users': ['userId', 'displayName', 'joinDate', 'usageCount', 'lastUsedDate', 'state'],
'logs': ['id', 'userId', 'input', 'output', 'createdAt']
};
if (headers[name]) {
sheet.getRange(1, 1, 1, headers[name].length).setValues([headers[name]]);
sheet.setFrozenRows(1);
}
},
// =====================
// 用戶管理
// =====================
/**
* 建立新用戶
*/
createUser(userId) {
const existingUser = this.getUser(userId);
if (existingUser) return existingUser;
const sheet = this.getSheet('users');
const profile = LineBot.getProfile(userId) || {};
const userData = [
userId,
profile.displayName || '',
new Date().toISOString(),
0, // usageCount
'', // lastUsedDate
'' // state (JSON)
];
sheet.appendRow(userData);
return this.getUser(userId);
},
/**
* 取得用戶資料
*/
getUser(userId) {
const sheet = this.getSheet('users');
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
if (data[i][0] === userId) {
return {
row: i + 1,
userId: data[i][0],
displayName: data[i][1],
joinDate: data[i][2],
usageCount: data[i][3] || 0,
lastUsedDate: data[i][4],
state: data[i][5] ? JSON.parse(data[i][5]) : null
};
}
}
return null;
},
/**
* 增加使用次數
*/
incrementUsage(userId) {
const user = this.getUser(userId);
if (!user) return;
const sheet = this.getSheet('users');
sheet.getRange(user.row, 4).setValue(user.usageCount + 1);
sheet.getRange(user.row, 5).setValue(new Date().toISOString());
},
/**
* 設定用戶狀態(用於多輪對話)
*/
setUserState(userId, state) {
let user = this.getUser(userId);
if (!user) {
this.createUser(userId);
user = this.getUser(userId);
}
const sheet = this.getSheet('users');
sheet.getRange(user.row, 6).setValue(JSON.stringify(state));
},
/**
* 取得用戶狀態
*/
getUserState(userId) {
const user = this.getUser(userId);
return user ? user.state : null;
},
/**
* 清除用戶狀態
*/
clearUserState(userId) {
const user = this.getUser(userId);
if (!user) return;
const sheet = this.getSheet('users');
sheet.getRange(user.row, 6).setValue('');
},
// =====================
// 使用記錄
// =====================
/**
* 儲存使用記錄
*/
saveLog(userId, input, output) {
const sheet = this.getSheet('logs');
const id = Utilities.getUuid();
sheet.appendRow([
id,
userId,
input.substring(0, 1000), // 限制長度
output.substring(0, 5000), // 限制長度
new Date().toISOString()
]);
return id;
},
/**
* 取得最近一次記錄(用於追問功能)
*/
getLastLog(userId, withinMinutes = 30) {
const sheet = this.getSheet('logs');
const data = sheet.getDataRange().getValues();
for (let i = data.length - 1; i >= 1; i--) {
if (data[i][1] === userId) {
const createdAt = new Date(data[i][4]);
const now = new Date();
const diffMinutes = (now - createdAt) / (1000 * 60);
if (diffMinutes <= withinMinutes) {
return {
input: data[i][2],
output: data[i][3],
createdAt: data[i][4]
};
}
break;
}
}
return null;
}
};
/**
* LINE Messaging API 封裝
*
* 此模組為通用模組,無需修改即可使用
*/
const LineBot = {
/**
* 回覆訊息(使用 replyToken,不消耗 push 額度)
*/
reply(replyToken, messages) {
const url = 'https://api.line.me/v2/bot/message/reply';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + Config.LINE_CHANNEL_ACCESS_TOKEN
},
payload: JSON.stringify({
replyToken: replyToken,
messages: messages
}),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
return JSON.parse(response.getContentText());
},
/**
* 主動推送訊息(消耗 push 額度)
*/
push(userId, messages) {
const url = 'https://api.line.me/v2/bot/message/push';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + Config.LINE_CHANNEL_ACCESS_TOKEN
},
payload: JSON.stringify({
to: userId,
messages: messages
}),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
return JSON.parse(response.getContentText());
},
/**
* 顯示思考中動畫
* @param {string} chatId - 用戶 ID
* @param {number} seconds - 持續秒數 (5-60)
*/
showLoading(chatId, seconds) {
const url = 'https://api.line.me/v2/bot/chat/loading/start';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + Config.LINE_CHANNEL_ACCESS_TOKEN
},
payload: JSON.stringify({
chatId: chatId,
loadingSeconds: Math.min(Math.max(seconds || 20, 5), 60)
}),
muteHttpExceptions: true
};
try {
UrlFetchApp.fetch(url, options);
} catch (e) {
Logger.log('showLoading error: ' + e.message);
}
},
/**
* 取得用戶資料
*/
getProfile(userId) {
const url = 'https://api.line.me/v2/bot/profile/' + userId;
const options = {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + Config.LINE_CHANNEL_ACCESS_TOKEN
},
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(url, options);
return JSON.parse(response.getContentText());
} catch (e) {
Logger.log('Error getting profile: ' + e);
return null;
}
},
/**
* 建立純文字訊息
*/
textMessage(text) {
return { type: 'text', text: text };
},
/**
* 驗證 Webhook 簽名
*/
verifySignature(body, signature) {
const hash = Utilities.computeHmacSha256Signature(body, Config.LINE_CHANNEL_SECRET);
const expectedSignature = Utilities.base64Encode(hash);
return signature === expectedSignature;
}
};
/**
* {{SKILL_NAME}} LINE Bot - Webhook 入口
*
* 純文字版本,穩定易除錯
*/
// ============================================
// Webhook 處理
// ============================================
function doPost(e) {
try {
const data = JSON.parse(e.postData.contents);
Logger.log('Webhook received: ' + JSON.stringify(data));
if (data.events) {
data.events.forEach(event => {
try {
handleEvent(event);
} catch (err) {
Logger.log('Event handling error: ' + err.message);
}
});
}
return ContentService.createTextOutput('OK');
} catch (error) {
Logger.log('doPost error: ' + error.message);
return ContentService.createTextOutput('ERROR');
}
}
function doGet(e) {
return ContentService.createTextOutput(Config.BOT_NAME + ' is running!');
}
// ============================================
// 事件處理
// ============================================
function handleEvent(event) {
const userId = event.source.userId;
const replyToken = event.replyToken;
// 處理加好友事件
if (event.type === 'follow') {
handleFollow(userId, replyToken);
return;
}
// 處理文字訊息
if (event.type === 'message' && event.message.type === 'text') {
const userMessage = event.message.text.trim();
handleTextMessage(userId, replyToken, userMessage);
return;
}
}
/**
* 處理新用戶加好友
*/
function handleFollow(userId, replyToken) {
// 建立用戶記錄
Database.createUser(userId);
// 發送歡迎訊息
const welcomeText = Config.WELCOME_MESSAGE ||
`歡迎使用 ${Config.BOT_NAME}!\n\n直接輸入訊息即可開始使用。`;
LineBot.reply(replyToken, [
LineBot.textMessage(welcomeText)
]);
}
/**
* 處理文字訊息
*/
function handleTextMessage(userId, replyToken, userMessage) {
// 顯示思考中動畫
LineBot.showLoading(userId, 30);
try {
// 呼叫 SkillCore 處理訊息
const response = SkillCore.processMessage(userId, userMessage);
// 回覆純文字
LineBot.reply(replyToken, [
LineBot.textMessage(response)
]);
// 記錄使用
Database.incrementUsage(userId);
Database.saveLog(userId, userMessage, response);
} catch (e) {
Logger.log('處理訊息錯誤:' + e.message);
LineBot.reply(replyToken, [
LineBot.textMessage('抱歉,處理您的訊息時發生錯誤,請稍後再試。')
]);
}
}
// ============================================
// 測試函數
// ============================================
/**
* 測試用:取得測試用戶 ID
*/
function getTestUserId() {
return PropertiesService.getScriptProperties().getProperty('TEST_USER_ID');
}
/**
* 測試 LINE 發送功能
*/
function testLineSend() {
const userId = getTestUserId();
if (!userId) {
Logger.log('請設定 TEST_USER_ID');
return;
}
LineBot.push(userId, [
LineBot.textMessage('✅ LINE Bot 連接正常!')
]);
Logger.log('✅ 測試訊息已發送');
}
/**
* 測試 SkillCore
*/
function testSkillCore() {
const userId = getTestUserId();
if (!userId) {
Logger.log('請設定 TEST_USER_ID');
return;
}
try {
const response = SkillCore.processMessage(userId, '測試訊息');
LineBot.push(userId, [
LineBot.textMessage('✅ SkillCore 測試結果:\n\n' + response)
]);
Logger.log('✅ SkillCore 測試完成');
} catch (e) {
LineBot.push(userId, [
LineBot.textMessage('❌ SkillCore 測試失敗:' + e.message)
]);
Logger.log('❌ SkillCore 測試失敗:' + e.message);
}
}
/**
* {{SKILL_NAME}} LINE Bot - OpenAI 整合
*
* 此模組提供 OpenAI API 呼叫功能
*/
const OpenAI = {
/**
* 呼叫 Chat Completions API
* @param {string} userMessage - 用戶訊息
* @param {string} systemPrompt - 系統提示詞
* @param {number} maxTokens - 最大回應長度
* @returns {string} AI 回應文字
*/
chat(userMessage, systemPrompt, maxTokens = 1000) {
const url = 'https://api.openai.com/v1/chat/completions';
const payload = {
model: Config.OPENAI_MODEL,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage }
],
max_tokens: maxTokens,
temperature: 0.7
};
try {
const response = UrlFetchApp.fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + Config.OPENAI_API_KEY,
'Content-Type': 'application/json'
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const result = JSON.parse(response.getContentText());
if (result.error) {
Logger.log('OpenAI error: ' + result.error.message);
return '抱歉,AI 處理時發生錯誤:' + result.error.message;
}
return result.choices[0].message.content.trim();
} catch (e) {
Logger.log('OpenAI exception: ' + e);
return '抱歉,AI 服務暫時無法使用,請稍後再試。';
}
},
/**
* 帶有對話歷史的 Chat(用於多輪對話)
* @param {Array} messages - 對話歷史 [{role, content}, ...]
* @param {string} systemPrompt - 系統提示詞
* @returns {string} AI 回應文字
*/
chatWithHistory(messages, systemPrompt, maxTokens = 1000) {
const url = 'https://api.openai.com/v1/chat/completions';
const fullMessages = [
{ role: 'system', content: systemPrompt },
...messages
];
const payload = {
model: Config.OPENAI_MODEL,
messages: fullMessages,
max_tokens: maxTokens,
temperature: 0.7
};
try {
const response = UrlFetchApp.fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + Config.OPENAI_API_KEY,
'Content-Type': 'application/json'
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const result = JSON.parse(response.getContentText());
if (result.error) {
Logger.log('OpenAI error: ' + result.error.message);
return '抱歉,AI 處理時發生錯誤。';
}
return result.choices[0].message.content.trim();
} catch (e) {
Logger.log('OpenAI exception: ' + e);
return '抱歉,AI 服務暫時無法使用,請稍後再試。';
}
},
/**
* 使用 Responses API(支援 file_search / Vector Store)
* 注意:需要設定 OPENAI_VECTOR_STORE_ID
*/
responsesWithSearch(userMessage, systemPrompt) {
if (!Config.OPENAI_VECTOR_STORE_ID) {
// 如果沒有 Vector Store,使用一般 chat
return this.chat(userMessage, systemPrompt);
}
const url = 'https://api.openai.com/v1/responses';
const payload = {
model: Config.OPENAI_MODEL,
input: userMessage,
instructions: systemPrompt,
tools: [{
type: 'file_search',
vector_store_ids: [Config.OPENAI_VECTOR_STORE_ID]
}]
};
try {
const response = UrlFetchApp.fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + Config.OPENAI_API_KEY,
'Content-Type': 'application/json'
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const result = JSON.parse(response.getContentText());
if (result.error) {
Logger.log('OpenAI Responses error: ' + result.error.message);
// 降級到一般 chat
return this.chat(userMessage, systemPrompt);
}
return result.output_text || result.output?.[0]?.content?.[0]?.text || '無法取得回應';
} catch (e) {
Logger.log('OpenAI Responses exception: ' + e);
return this.chat(userMessage, systemPrompt);
}
}
};
/**
* {{SKILL_NAME}} LINE Bot - Skill 核心邏輯
*
* ⚠️ 此檔案需要根據具體 Skill 進行客製化
*
* AI 將會根據分析的 Skill 內容填充以下區塊:
* - SYSTEM_PROMPT: 系統提示詞
* - processMessage(): 訊息處理邏輯
*/
const SkillCore = {
// ============================================
// 系統提示詞(由 AI 根據 Skill 生成)
// ============================================
SYSTEM_PROMPT: `{{SYSTEM_PROMPT}}`,
// ============================================
// 主要處理函數
// ============================================
/**
* 處理用戶訊息
* @param {string} userId - 用戶 ID
* @param {string} userMessage - 用戶訊息
* @returns {string} 回應文字
*/
processMessage(userId, userMessage) {
{{PROCESS_MESSAGE_LOGIC}}
},
// ============================================
// 輔助函數(可選,由 AI 根據需要添加)
// ============================================
{{HELPER_FUNCTIONS}}
};