UNPKG

cw-ai-backlog

Version:

AI-powered tool to generate backlog items from meeting documents and output to Google Sheets

287 lines (245 loc) 10 kB
const readline = require('readline'); const fs = require('fs-extra'); const path = require('path'); const os = require('os'); const chalk = require('chalk'); /** * 建立 readline 介面 */ function createInterface() { return readline.createInterface({ input: process.stdin, output: process.stdout }); } /** * 詢問問題並取得回答 * @param {Object} rl readline 介面 * @param {string} question 問題 * @returns {Promise<string>} 使用者回答 */ function askQuestion(rl, question) { return new Promise((resolve) => { rl.question(question, (answer) => { resolve(answer.trim()); }); }); } /** * 驗證檔案路徑是否存在 * @param {string} filePath 檔案路徑 * @returns {boolean} 檔案是否存在 */ async function validateFilePath(filePath) { if (!filePath) return false; try { await fs.access(filePath); return true; } catch { return false; } } /** * 驗證 URL 格式 * @param {string} url URL * @returns {boolean} URL 是否有效 */ function validateUrl(url) { if (!url) return false; try { new URL(url); return url.includes('docs.google.com/spreadsheets'); } catch { return false; } } /** * 互動式建立配置檔案 */ async function setupConfigInteractive() { const rl = createInterface(); console.log(chalk.blue('\n🚀 AI Backlog Generator 配置設定精靈\n')); console.log(chalk.yellow('我將引導您完成配置設定,請按照提示輸入相關資訊。\n')); const config = { apiKey: null, model: 'gpt-4o', keepPhrases: [], customPrompt: [], googleCredentials: null, googleSheetUrl: null, maxTokens: 4000, temperature: 0.1 }; try { // 1. OpenAI API Key console.log(chalk.green('1. OpenAI API Key 設定')); console.log(chalk.gray(' 您可以從 https://platform.openai.com/api-keys 取得 API Key')); let apiKey = ''; while (!apiKey) { apiKey = await askQuestion(rl, chalk.cyan(' 請輸入您的 OpenAI API Key: ')); if (!apiKey) { console.log(chalk.red(' ❌ API Key 不能為空,請重新輸入')); } else if (!apiKey.startsWith('sk-')) { console.log(chalk.red(' ❌ API Key 格式不正確,應該以 "sk-" 開頭')); apiKey = ''; } } config.apiKey = apiKey; console.log(chalk.green(' ✅ API Key 設定完成\n')); // 2. AI 模型選擇 console.log(chalk.green('2. AI 模型選擇')); console.log(chalk.gray(' 可選擇: gpt-4o (推薦), gpt-4-turbo, gpt-3.5-turbo')); const model = await askQuestion(rl, chalk.cyan(' 請選擇模型 [gpt-4o]: ')) || 'gpt-4o'; const validModels = ['gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo']; if (validModels.includes(model)) { config.model = model; console.log(chalk.green(` ✅ 已選擇模型: ${model}\n`)); } else { console.log(chalk.yellow(` ⚠️ 無效的模型,使用預設值: gpt-4o\n`)); } // 3. Google 憑證檔案 console.log(chalk.green('3. Google API 憑證設定')); console.log(chalk.gray(' 需要 Google Service Account JSON 憑證檔案來存取 Google Docs 和 Sheets')); console.log(chalk.gray(' 取得方式: https://console.cloud.google.com > API 和服務 > 憑證')); console.log(chalk.yellow(' ⚠️ 如果只解析本地檔案,可以跳過此步驟')); let credentialsPath = ''; while (!credentialsPath) { const inputPath = await askQuestion(rl, chalk.cyan(' 請輸入憑證檔案的完整路徑(或輸入 "skip" 跳過): ')); if (!inputPath) { console.log(chalk.red(' ❌ 請輸入路徑或 "skip"')); continue; } if (inputPath.toLowerCase() === 'skip') { console.log(chalk.yellow(' ⚠️ 跳過憑證設定')); console.log(chalk.yellow(' ⚠️ 限制:無法解析私人 Google Docs、無法寫入 Google Sheets\n')); break; } const expandedPath = inputPath.replace('~', os.homedir()); if (await validateFilePath(expandedPath)) { credentialsPath = expandedPath; console.log(chalk.green(' ✅ 憑證檔案驗證成功\n')); } else { console.log(chalk.red(` ❌ 檔案不存在: ${expandedPath}`)); } } config.googleCredentials = credentialsPath || null; // 4. Google Sheets URL console.log(chalk.green('4. Google Sheets URL 設定')); console.log(chalk.gray(' 需要一個 Google Sheets 來儲存生成的 backlog')); console.log(chalk.gray(' 請確保 Service Account 有編輯此 Sheets 的權限')); if (!config.googleCredentials) { console.log(chalk.yellow(' ⚠️ 未設定 Google 憑證,將無法寫入 Google Sheets')); console.log(chalk.yellow(' ⚠️ 建議跳過此設定,或先設定憑證檔案')); } let sheetsUrl = ''; while (!sheetsUrl) { const inputUrl = await askQuestion(rl, chalk.cyan(' 請輸入 Google Sheets URL(或輸入 "skip" 跳過): ')); if (!inputUrl) { console.log(chalk.red(' ❌ 請輸入 URL 或 "skip"')); continue; } if (inputUrl.toLowerCase() === 'skip') { console.log(chalk.yellow(' ⚠️ 跳過 Sheets URL 設定')); console.log(chalk.yellow(' ⚠️ 限制:無法自動寫入 Google Sheets\n')); break; } if (validateUrl(inputUrl)) { sheetsUrl = inputUrl; console.log(chalk.green(' ✅ Google Sheets URL 驗證成功\n')); } else { console.log(chalk.red(' ❌ URL 格式不正確或不是 Google Sheets URL')); console.log(chalk.gray(' 正確格式: https://docs.google.com/spreadsheets/d/your-sheet-id/edit')); } } config.googleSheetUrl = sheetsUrl || null; // 5. 進階設定(可選) console.log(chalk.green('5. 進階設定(可選)')); const setupAdvanced = await askQuestion(rl, chalk.cyan(' 是否要設定進階選項?(y/N): ')); if (setupAdvanced.toLowerCase() === 'y' || setupAdvanced.toLowerCase() === 'yes') { // 保留字設定 console.log(chalk.gray('\n 保留字設定 - 在分析時需要特別注意的詞彙')); const keepPhrasesInput = await askQuestion(rl, chalk.cyan(' 請輸入保留字(用逗號分隔): ')); if (keepPhrasesInput) { config.keepPhrases = keepPhrasesInput.split(',').map(phrase => phrase.trim()).filter(phrase => phrase); } // 自訂 prompt console.log(chalk.gray('\n 自訂 Prompt - 額外的分析指示')); const customPromptInput = await askQuestion(rl, chalk.cyan(' 請輸入自訂指示: ')); if (customPromptInput) { config.customPrompt = [customPromptInput]; } } // 6. 選擇配置檔案位置 console.log(chalk.green('\n6. 配置檔案儲存位置')); const configOptions = [ { path: path.join(os.homedir(), '.ai_backlog.config.js'), description: '全域配置(推薦)' }, { path: './ai_backlog.config.js', description: '當前目錄' } ]; console.log(chalk.gray(' 請選擇配置檔案儲存位置:')); configOptions.forEach((option, index) => { console.log(chalk.gray(` ${index + 1}. ${option.description} (${option.path})`)); }); let configChoice = ''; while (!configChoice) { const choice = await askQuestion(rl, chalk.cyan(' 請選擇 (1 或 2) [1]: ')) || '1'; if (choice === '1' || choice === '2') { configChoice = choice; } else { console.log(chalk.red(' ❌ 請輸入 1 或 2')); } } const selectedConfigPath = configOptions[parseInt(configChoice) - 1].path; // 7. 生成配置檔案 console.log(chalk.green('\n7. 生成配置檔案')); const configContent = generateConfigContent(config); await fs.writeFile(selectedConfigPath, configContent); console.log(chalk.green(` ✅ 配置檔案已建立: ${selectedConfigPath}`)); // 8. 完成提示 console.log(chalk.blue('\n🎉 配置設定完成!\n')); console.log(chalk.green('✅ 下一步操作:')); console.log(chalk.cyan(' 1. 確保 Google Service Account 有存取目標文件和 Sheets 的權限')); console.log(chalk.cyan(' 2. 執行: ai-backlog "your-google-docs-url"')); console.log(chalk.cyan(' 3. 查看生成的 Google Sheets 結果')); if (!config.googleCredentials || !config.googleSheetUrl) { console.log(chalk.yellow('\n⚠️ 請注意:')); if (!config.googleCredentials) { console.log(chalk.yellow(' - 尚未設定 Google 憑證檔案')); } if (!config.googleSheetUrl) { console.log(chalk.yellow(' - 尚未設定 Google Sheets URL')); } console.log(chalk.yellow(' 請稍後編輯配置檔案補充這些設定')); } } catch (error) { console.error(chalk.red('\n❌ 設定過程中發生錯誤:', error.message)); } finally { rl.close(); } } /** * 生成配置檔案內容 * @param {Object} config 配置物件 * @returns {string} 配置檔案內容 */ function generateConfigContent(config) { return `module.exports = { // OpenAI API Key (必填) apiKey: '${config.apiKey}', // 使用的模型 model: '${config.model}', // 保留字 - 在分析時需要特別注意的詞彙 keepPhrases: ${JSON.stringify(config.keepPhrases, null, 4)}, // 自訂 prompt 指示 - 會加入到 AI 分析指令中 customPrompt: ${JSON.stringify(config.customPrompt, null, 4)}, // Google API 憑證檔案路徑 (必填) googleCredentials: ${config.googleCredentials ? `'${config.googleCredentials}'` : 'null'}, // Google Sheets URL (必填 - 用於輸出 backlog) googleSheetUrl: ${config.googleSheetUrl ? `'${config.googleSheetUrl}'` : 'null'}, // AI 參數設定 maxTokens: ${config.maxTokens}, temperature: ${config.temperature} };`; } module.exports = { setupConfigInteractive };