import { accountLinkingService } from './account-linking.server'; import { githubApp } from './github-app.server'; // Default API URL const DEFAULT_API_URL = 'https://huggingface-hugex-explore.hf.space/api'; interface HugExRepository { url: string; } interface HugExEnvironment { LLM_MODEL: string; LLM_PROVIDER: string; } interface HugExSecrets { [key: string]: string; } export interface HugExJobCreationParams { title: string; description: string; repository: HugExRepository; branch: string; environment: HugExEnvironment; secrets: HugExSecrets; } export interface HugExJob { id: string; status: string; createdAt: string; title: string; repository: HugExRepository; } export class HugExService { private apiUrl: string; constructor() { this.apiUrl = process.env.HUGEX_API_URL || DEFAULT_API_URL; // Log configuration status if (!process.env.HUGEX_API_URL) { console.log(`â„šī¸ Using default HugEx API URL: ${this.apiUrl}`); } else { console.log(`✅ HugEx API configured: ${this.apiUrl}`); } } /** * Process a GitHub issue to check for @hugex mentions and create jobs */ async processGitHubIssue(issue: any, repository: any): Promise { try { // Check if issue body contains @hugex mention if (!issue.body || !issue.body.includes('@hugex')) { return false; } console.log(`🤖 Found @hugex mention in issue #${issue.number}: ${issue.title}`); // Get GitHub user's linked HF account const githubUserId = issue.user.id.toString(); const accountLink = accountLinkingService.findByGitHubUser(githubUserId); if (!accountLink) { console.log(`âš ī¸ No linked HuggingFace account found for GitHub user: ${issue.user.login}`); // Comment on the issue to inform the user they need to link their account await this.commentOnIssue( repository.owner.login, repository.name, issue.number, `âš ī¸ @${issue.user.login}, to use @hugex, you need to link your GitHub account with your HuggingFace account. Please visit the dashboard to complete this process.` ); return false; } // Extract job parameters from issue body const jobParams = await this.extractJobParamsFromIssue(issue, repository); // Create HugEx job const jobId = await this.createJob(accountLink.huggingfaceUsername, jobParams); if (jobId) { console.log(`✅ Created HugEx job ${jobId} for issue #${issue.number}`); // Comment on the issue with job info await this.commentOnIssue( repository.owner.login, repository.name, issue.number, `🚀 Created HugEx job for this issue. You can track progress at ${this.apiUrl}/jobs/${jobId}` ); return true; } return false; } catch (error) { console.error('Error processing GitHub issue for HugEx:', error); return false; } } /** * Extract job parameters from issue body */ private async extractJobParamsFromIssue(issue: any, repository: any): Promise { // Default values const params: HugExJobCreationParams = { title: issue.title, description: issue.body, repository: { url: repository.html_url }, branch: repository.default_branch || 'main', environment: { LLM_MODEL: 'gpt-4', LLM_PROVIDER: 'openai' }, secrets: {} }; // Extract custom parameters if available try { // Look for JSON block in the issue body const jsonMatch = issue.body.match(/```json\s*([\s\S]*?)\s*```/); if (jsonMatch && jsonMatch[1]) { const customParams = JSON.parse(jsonMatch[1]); // Merge with defaults, keeping required structure if (customParams.title) params.title = customParams.title; if (customParams.description) params.description = customParams.description; if (customParams.repository && customParams.repository.url) { params.repository.url = customParams.repository.url; } if (customParams.branch) params.branch = customParams.branch; if (customParams.environment) params.environment = { ...params.environment, ...customParams.environment }; if (customParams.secrets) params.secrets = { ...params.secrets, ...customParams.secrets }; } } catch (error) { console.warn('Failed to parse custom parameters from issue body:', error); // Continue with default parameters } return params; } /** * Create a new HugEx job */ async createJob(huggingfaceUsername: string, params: HugExJobCreationParams): Promise { try { // Get HuggingFace token from persistent storage // Note: In a real implementation, you'd need a secure way to store and retrieve HF tokens // This is a placeholder - you need to implement token storage const hfToken = await this.getHuggingFaceToken(huggingfaceUsername); if (!hfToken) { console.error(`No HuggingFace token available for user: ${huggingfaceUsername}`); return null; } const response = await fetch(`${this.apiUrl}/jobs/create-with-key`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${hfToken}` }, body: JSON.stringify(params) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to create HugEx job: ${response.status} - ${errorText}`); } const responseData = await response.json(); return responseData.id || null; } catch (error) { console.error('Error creating HugEx job:', error); return null; } } /** * Comment on a GitHub issue */ private async commentOnIssue(owner: string, repo: string, issueNumber: number, body: string): Promise { try { // Get installation Octokit instance for the repository const installation = await githubApp.getInstallationByRepo(owner, repo); if (!installation) { throw new Error(`No GitHub App installation found for repository: ${owner}/${repo}`); } const octokit = await githubApp.getInstallationOctokit(installation.id); await octokit.rest.issues.createComment({ owner, repo, issue_number: issueNumber, body }); console.log(`✅ Posted comment on ${owner}/${repo}#${issueNumber}`); } catch (error) { console.error('Error commenting on GitHub issue:', error); } } /** * Get HuggingFace token for a user from the account link */ private async getHuggingFaceToken(username: string): Promise { // Look up the user's account link to get the stored token const accountLink = accountLinkingService.findByHuggingFaceUser(username); if (accountLink && accountLink.huggingfaceAccessToken) { console.log(`✅ Retrieved HuggingFace access token for user: ${username}`); return accountLink.huggingfaceAccessToken; } // Fall back to default token if no specific token is found console.warn(`âš ī¸ No HuggingFace access token found for user: ${username}. Using default token if available.`); return process.env.HF_DEFAULT_TOKEN || null; } } // Singleton instance export const hugexService = new HugExService();