Skip to main content
Glama
ai-service.js9.17 kB
/** * AI 服務 * 支援 OpenAI、Anthropic、Google Gemini */ const SYSTEM_PROMPT = `你是一位專精於 Web API 現代化和瀏覽器相容性的資深前端專家。 請分析以下 PR 的程式碼變更,**整合規則式分析和 AI 分析**,提供完整的現代化建議。 ## 分析重點 ### 1. 函式庫替換建議 檢查是否使用了可被原生 Web API 替代的第三方函式庫: - jQuery → 原生 DOM API (querySelector, classList, fetch 等) - Moment.js → Date-fns / Dayjs / Temporal API - Lodash → 原生陣列方法 (map, filter, reduce, find 等) - Axios → 原生 fetch API - Bluebird → 原生 Promise - uuid → crypto.randomUUID() - 其他可替代的 polyfill 函式庫 ### 2. API 現代化建議 檢查是否使用了過時的 API 或語法: - XMLHttpRequest → fetch API - var → let/const - callback → Promise/async-await - document.write → DOM API - eval → 安全替代方案 - for 迴圈 → map/filter/forEach - arguments → rest parameters ### 3. 瀏覽器相容性檢查 指出可能有相容性問題的新 API: - 標記需要 Polyfill 的 API - 提供 Polyfill CDN 連結建議 - 建議檢查 Can I Use 的 API ### 4. 現代 Web API 推薦 針對特定需求推薦更好的原生 API: - 懶加載 → IntersectionObserver - 元件尺寸監聽 → ResizeObserver - DOM 變化監聽 → MutationObserver - 平滑動畫 → requestAnimationFrame / Web Animations API - 剪貼簿操作 → Clipboard API ## 整合規則式分析結果 如果提供了規則式分析的結果(ruleBasedAnalysis),請: 1. **優先參考規則式分析的建議**:這些是基於規則資料庫的客觀分析 2. **結合 AI 分析**:基於 PR diff 的內容,提供更深入的建議 3. **補充規則式分析未涵蓋的部分**:例如針對特定使用場景的優化建議 4. **驗證規則式分析的建議**:確認這些建議是否適用於當前的 PR 變更 ## 參考資料 提供建議時,請盡可能引用 MDN Web Docs 的資料作為參考來源,並附上相關連結。 例如: - fetch API: https://developer.mozilla.org/docs/Web/API/Fetch_API - IntersectionObserver: https://developer.mozilla.org/docs/Web/API/IntersectionObserver ## 輸出格式 請用繁體中文、條理分明地回覆,包含: ### 1. PR 變更摘要 - 這個 PR 做了什麼變更 - 變更的檔案和範圍 ### 2. 現代化建議(整合規則式分析和 AI 分析) - **規則式分析結果**(如果提供):列出規則式分析發現的問題 - **AI 分析補充**:基於 PR diff 的深入建議 - **具體的改進建議**:附程式碼範例和 MDN 連結 ### 3. Web API 優化建議 - 針對 PR 中的功能需求,推薦更適合的現代 Web API - 參考完整的 Web API 類別列表(如果提供) ### 4. 相容性注意事項 - 需要注意的瀏覽器相容性問題(附 Can I Use 或 MDN 連結) - Polyfill 建議和 CDN 連結 ### 5. 風險評估 - 評估變更的風險等級(low/medium/high) - 預估實施工時 - 是否為破壞性變更 ### 6. 優先順序建議 - 按照「低風險、高效益」排序建議 - 標記「快速勝利」項目(< 3 小時、低風險) ### 7. 整體評估 - 這個 PR 的程式碼品質評估`; // 提供者配置 const PROVIDERS = { openai: { name: 'OpenAI', url: 'https://api.openai.com/v1/chat/completions', defaultModel: 'gpt-4o', formatRequest: (model, systemPrompt, userPrompt, apiKey) => ({ url: 'https://api.openai.com/v1/chat/completions', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: { model: model, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: userPrompt } ], max_tokens: 4096 } }), parseResponse: (response) => response.choices[0].message.content }, anthropic: { name: 'Anthropic', url: 'https://api.anthropic.com/v1/messages', defaultModel: 'claude-sonnet-4-20250514', formatRequest: (model, systemPrompt, userPrompt, apiKey) => ({ url: 'https://api.anthropic.com/v1/messages', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }, body: { model: model, max_tokens: 4096, system: systemPrompt, messages: [ { role: 'user', content: userPrompt } ] } }), parseResponse: (response) => response.content[0].text }, gemini: { name: 'Google Gemini', defaultModel: 'gemini-2.5-flash', formatRequest: (model, systemPrompt, userPrompt, apiKey) => ({ url: `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`, headers: { 'Content-Type': 'application/json' }, body: { system_instruction: { parts: [{ text: systemPrompt }] }, contents: [ { parts: [{ text: userPrompt }] } ], generationConfig: { maxOutputTokens: 8192 } } }), parseResponse: (response) => { // 檢查回應結構 if (!response.candidates || response.candidates.length === 0) { throw new Error('Gemini 回應中沒有 candidates'); } const candidate = response.candidates[0]; // 檢查是否有 content if (!candidate.content || !candidate.content.parts || candidate.content.parts.length === 0) { // 檢查是否有錯誤訊息 if (candidate.finishReason === 'SAFETY') { throw new Error('Gemini 因安全性原因拒絕回應'); } throw new Error(`Gemini 回應格式異常: ${JSON.stringify(candidate)}`); } // 合併所有文字 parts const textParts = candidate.content.parts .filter(part => part.text) .map(part => part.text); if (textParts.length === 0) { throw new Error('Gemini 回應中沒有文字內容'); } return textParts.join('\n'); } } }; /** * 呼叫 AI API 分析程式碼 * @param {Object} options * @param {string} options.provider - AI 提供者 (openai, anthropic, gemini) * @param {string} options.model - 模型名稱(可選,使用預設) * @param {string} options.apiKey - API 金鑰 * @param {string} options.diff - PR 的 diff 內容 * @param {Object} options.ruleBasedAnalysis - 規則式分析的結果(可選) * @param {Array<string>} options.changedFiles - PR 變更的檔案列表(可選) * @returns {Promise<string>} AI 生成的分析報告 */ export async function analyzeWithAI({ provider, model, apiKey, diff, ruleBasedAnalysis = null, changedFiles = [] }) { const providerConfig = PROVIDERS[provider]; if (!providerConfig) { throw new Error(`不支援的 AI 提供者: ${provider}。支援的提供者: openai, anthropic, gemini`); } if (!apiKey) { throw new Error(`未提供 ${providerConfig.name} 的 API 金鑰`); } const modelToUse = model || providerConfig.defaultModel; // 構建用戶提示 let userPrompt = `請分析以下 PR 的程式碼變更:\n\n## PR Diff\n\`\`\`diff\n${diff}\n\`\`\`\n`; // 添加變更檔案列表 if (changedFiles && changedFiles.length > 0) { userPrompt += `\n## 變更的檔案\n\n`; changedFiles.forEach(file => { userPrompt += `- ${file}\n`; }); userPrompt += `\n`; } // 添加規則式分析結果 if (ruleBasedAnalysis) { userPrompt += `\n## 規則式分析結果\n\n`; userPrompt += `以下是針對 PR 變更檔案的規則式分析結果:\n\n`; userPrompt += `**掃描檔案數量**: ${ruleBasedAnalysis.summary.totalFiles}\n`; userPrompt += `**發現建議數量**: ${ruleBasedAnalysis.summary.totalSuggestions}\n`; userPrompt += `**風險等級**: ${ruleBasedAnalysis.summary.risk}\n`; if (ruleBasedAnalysis.report) { userPrompt += `\n### 詳細分析報告\n\n${ruleBasedAnalysis.report}\n\n`; } userPrompt += `\n**請整合以上規則式分析的結果,結合 PR diff 的內容,提供更深入的建議。**\n`; } const request = providerConfig.formatRequest(modelToUse, SYSTEM_PROMPT, userPrompt, apiKey); try { const response = await fetch(request.url, { method: 'POST', headers: request.headers, body: JSON.stringify(request.body) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`${providerConfig.name} API 錯誤 (${response.status}): ${errorText}`); } const data = await response.json(); return providerConfig.parseResponse(data); } catch (error) { if (error.message.includes('API 錯誤')) { throw error; } throw new Error(`呼叫 ${providerConfig.name} API 失敗: ${error.message}`); } } /** * 取得支援的提供者列表 */ export function getSupportedProviders() { return Object.keys(PROVIDERS); } /** * 取得提供者的預設模型 */ export function getDefaultModel(provider) { return PROVIDERS[provider]?.defaultModel || null; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mukiwu/dev-advisor-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server