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
JavaScript
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
};