-
Notifications
You must be signed in to change notification settings - Fork 355
Add AI recommendation feature using GitHub username input #1344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1d7c541
8da9ee9
a22ca66
bf5f147
81a3de2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // api/github/index.ts | ||
| import { patch, post } from '../..'; | ||
| import { | ||
| AnalyzeUserPayload, | ||
| AnalyzeUserResponse, | ||
| GetIssueMetricsPayload, | ||
| IssueMetricsResponse, | ||
| GetPRMetricsPayload, | ||
| PRMetricsResponse, | ||
| GetLanguagesPayload, | ||
| LanguagesResponse, | ||
| GetFilesPayload, | ||
| GetUserReposPayload, | ||
| UserReposResponse, | ||
| } from './typing'; | ||
| import { ApiResponse, BaseApiResponse } from '../../typings'; | ||
|
|
||
| export const analyzeUser = async (payload: AnalyzeUserPayload) => { | ||
| return post< | ||
| AnalyzeUserPayload, | ||
| ApiResponse<AnalyzeUserResponse> | ||
| >('/api/github/analyze-user', true, payload); | ||
| }; | ||
|
|
||
| export const getIssueMetrics = async (payload: GetIssueMetricsPayload) => { | ||
| return post< | ||
| GetIssueMetricsPayload, | ||
| ApiResponse<IssueMetricsResponse> | ||
| >('/api/github/issue-metrics', true, payload); | ||
| }; | ||
|
|
||
| export const getPRMetrics = async (payload: GetPRMetricsPayload) => { | ||
| return post< | ||
| GetPRMetricsPayload, | ||
| ApiResponse<PRMetricsResponse> | ||
| >('/api/github/pr-metrics', true, payload); | ||
| }; | ||
|
|
||
| export const getLanguages = async (payload: GetLanguagesPayload) => { | ||
| return post< | ||
| GetLanguagesPayload, | ||
| ApiResponse<LanguagesResponse> | ||
| >('/api/github/languages', true, payload); | ||
| }; | ||
|
|
||
| export const getFiles = async (payload: GetFilesPayload) => { | ||
| return post< | ||
| GetFilesPayload, | ||
| ApiResponse<any[]> | ||
| >('/api/github/files', true, payload); | ||
| }; | ||
|
|
||
| export const getUserRepos = async (payload: GetUserReposPayload) => { | ||
| return post< | ||
| GetUserReposPayload, | ||
| ApiResponse<UserReposResponse> | ||
| >('/api/github/user-repos', true, payload); | ||
| }; | ||
|
|
||
| export const detectCapabilities = async (payload: { | ||
| files: any[]; | ||
| deps?: string[]; | ||
| }) => { | ||
| return post< | ||
| { files: any[]; deps?: string[] }, | ||
| ApiResponse<string[]> | ||
| >('/api/github/detect-capabilities', true, payload); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| export interface AnalyzeUserPayload{ | ||
| username: string; | ||
| } | ||
| export interface AnalyzeUserResponse{ | ||
| repos: Array<{ | ||
| repo:string; | ||
| languages: string[]; | ||
| capabilities: string[]; | ||
| avgPRMergeTime: number | null; | ||
| avgIssueResolutionTime: number | null; | ||
| } >; | ||
| } | ||
| export interface GetIssueMetricsPayload{ | ||
| owner: string; | ||
| repo: string; | ||
| } | ||
|
|
||
| export interface IssueMetricsResponse{ | ||
| issue: Array<{ | ||
| issue: number; | ||
| resolutionHours: number; | ||
| }>; | ||
| } | ||
|
|
||
| export interface GetPRMetricsPayload{ | ||
| owner : string; | ||
| repo: string ; | ||
| } | ||
|
|
||
| export interface PRMetricsResponse{ | ||
| prs:Array<{ | ||
| prnumber:number; | ||
| mergetimehours:number; | ||
| }>; | ||
| } | ||
|
|
||
| export interface GetLanguagesPayload{ | ||
| owner:string; | ||
| repo:string; | ||
| } | ||
|
|
||
| export interface LanguagesResponse { | ||
| [language: string]: number; | ||
| } | ||
|
|
||
| export interface GetFilesPayload { | ||
| owner: string; | ||
| repo: string; | ||
| } | ||
|
|
||
| export interface GetUserReposPayload { | ||
| username: string; | ||
| } | ||
|
|
||
| export interface UserReposResponse { | ||
| repos: Array<{ | ||
| owner: { login: string }; | ||
| name: string; | ||
| }>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { useEffect, useState } from "react"; | ||
|
|
||
| export default function Recommendation() { | ||
| const [result, setResult] = useState(""); | ||
| const [loading, setLoading] = useState(true); | ||
|
|
||
| useEffect(() => { | ||
| const fetchRecommendation = async () => { | ||
| try { | ||
| const res = await fetch("http://localhost:5000/api/recommend", { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the created apis function instead of using |
||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ platform: "facebook" }) | ||
| }); | ||
|
|
||
| const data = await res.json(); | ||
| setResult(data.recommendation); | ||
| } catch (err) { | ||
| setResult("Error getting recommendation"); | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| fetchRecommendation(); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div> | ||
| {loading && <p>Generating AI recommendation...</p>} | ||
| {!loading && <pre>{result}</pre>} | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { GEMINI_API_KEY } from "./src/config/env.js"; | ||
|
|
||
| export async function getAIRecommendation(analysisData) { | ||
| const prompt = ` | ||
| You are an AI recommendation system. | ||
| Based on the following analysis of repos of github, generate clear recommendations of what tech stack should be learned to improve skills according to tech stack and also tell some future project ideas that can be built using the current skills and the recommended skills. | ||
| Analysis: | ||
| ${JSON.stringify(analysisData, null, 2)} | ||
| `; | ||
|
|
||
| const response = await fetch( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use axios instead of fetch |
||
| `https://generativelanguage.googleapis.com/v1/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}`, | ||
|
|
||
|
|
||
| { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| contents: [ | ||
| { | ||
| parts: [{ text: prompt }] | ||
| } | ||
| ] | ||
| }) | ||
| } | ||
| ); | ||
|
|
||
| const data = await response.json(); | ||
|
|
||
| console.log("Raw Gemini response:", JSON.stringify(data, null, 2)); | ||
| if (!data.candidates || data.candidates.length === 0) { | ||
| throw new Error("Gemini returned no response"); | ||
| } | ||
|
|
||
| return data.candidates[0].content.parts[0].text; | ||
|
|
||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the usecase of this file? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import "dotenv/config"; | ||
|
|
||
| import { analyzeUser } from "./src/services/index.js"; | ||
| import { getAIRecommendation } from "./gemini.js"; | ||
|
|
||
| async function run() { | ||
| try { | ||
| const analysis = await analyzeUser("facebook"); | ||
| await getAIRecommendation({ test: "hello" }); | ||
| const aiResult = await getAIRecommendation(analysis); | ||
| console.log(aiResult); | ||
| } catch (err) { | ||
| console.error(err); | ||
| } | ||
| } | ||
|
|
||
| run(); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,3 +40,6 @@ export const CLOUDINARY_API_SECRET = | |
| export const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'dev.admin@example.com'; | ||
| export const RESEND_API_KEY = | ||
| process.env.RESEND_API_KEY || 'dev_resend_key_abc123'; | ||
|
|
||
| // AI Configuration | ||
| export const GEMINI_API_KEY = process.env.GEMINI_API_KEY; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add some optional OR value. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| /** | ||
| * Analyze User - Comprehensive GitHub user analysis | ||
| * Gathers repositories, analyzes code patterns, and computes metrics | ||
| */ | ||
|
|
||
| import { fetchGitHubRepos } from './fetch-github-repos.js'; | ||
| import { fetchRepositoryFiles } from './fetch-repository-files.js'; | ||
| import { fetchRepositoryLanguages } from './fetch-repository-languages.js'; | ||
| import { fetchPullRequestMetrics } from './fetch-pull-request-metrics.js'; | ||
| import { fetchIssueMetrics } from './fetch-issue-metrics.js'; | ||
| import { detectCapabilities } from './detect-capabilities.js'; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these files are not required. Try to collab them into a single file. |
||
|
|
||
| /** | ||
| * Analyzes a GitHub user's repositories and generates capability metrics | ||
| * Fetches repo information, analyzes files, and computes PR/issue metrics | ||
| * @param {string} username - GitHub username to analyze | ||
| * @returns {Promise<Array<Object>>} Analysis results per repository | ||
| * @throws {Error} If user not found or API fails | ||
| */ | ||
| export async function analyzeUser(username) { | ||
| if (!username || typeof username !== 'string') { | ||
| throw new Error('Invalid username: must be a non-empty string'); | ||
| } | ||
|
|
||
| try { | ||
| const repos = await fetchGitHubRepos(username); | ||
| const analysisResults = []; | ||
|
|
||
| for (const repo of repos) { | ||
| console.log('Analyzing repo:', repo.owner.login, repo.name); | ||
|
|
||
| try { | ||
| // Fetch repository files | ||
| console.log('Fetching files...'); | ||
| const files = await fetchRepositoryFiles( | ||
| repo.owner.login, | ||
| repo.name | ||
| ); | ||
|
|
||
| // Detect capabilities based on files | ||
| const capabilities = detectCapabilities(files, []); | ||
|
|
||
| // Fetch PR metrics | ||
| console.log('Fetching PR metrics...'); | ||
| const prMetrics = await fetchPullRequestMetrics( | ||
| repo.owner.login, | ||
| repo.name | ||
| ); | ||
|
|
||
| // Fetch issue metrics | ||
| console.log('Fetching issue metrics...'); | ||
| const issueMetrics = await fetchIssueMetrics( | ||
| repo.owner.login, | ||
| repo.name | ||
| ); | ||
|
|
||
| // Fetch languages | ||
| console.log('Fetching languages...'); | ||
| const languages = await fetchRepositoryLanguages(repo); | ||
|
|
||
| // Calculate averages | ||
| const avgPRMergeTime = | ||
| prMetrics.length | ||
| ? prMetrics.reduce((a, b) => a + b.mergeTimeHours, 0) / | ||
| prMetrics.length | ||
| : null; | ||
|
|
||
| const avgIssueResolutionTime = | ||
| issueMetrics.length | ||
| ? issueMetrics.reduce((a, b) => a + b.resolutionTimeHours, 0) / | ||
| issueMetrics.length | ||
| : null; | ||
|
|
||
| analysisResults.push({ | ||
| repo: repo.name, | ||
| languages: Object.keys(languages), | ||
| capabilities, | ||
| avgPRMergeTime, | ||
| avgIssueResolutionTime | ||
| }); | ||
| } catch (repoError) { | ||
| console.error(`Error analyzing repo ${repo.name}:`, repoError.message); | ||
| // Continue with next repo even if one fails | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| return analysisResults; | ||
| } catch (error) { | ||
| throw new Error( | ||
| `Failed to analyze user ${username}: ${error.message}` | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import this in navbar to make it workable.