|
import { accountLinkingService } from './account-linking.server'; |
|
import { githubApp } from './github-app.server'; |
|
|
|
|
|
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; |
|
|
|
|
|
if (!process.env.HUGEX_API_URL) { |
|
console.log(`βΉοΈ Using default HugEx API URL: ${this.apiUrl}`); |
|
} else { |
|
console.log(`β
HugEx API configured: ${this.apiUrl}`); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
async processGitHubIssue(issue: any, repository: any): Promise<boolean> { |
|
try { |
|
|
|
if (!issue.body || !issue.body.includes('@hugex')) { |
|
return false; |
|
} |
|
|
|
console.log(`π€ Found @hugex mention in issue #${issue.number}: ${issue.title}`); |
|
|
|
|
|
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}`); |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
const jobParams = await this.extractJobParamsFromIssue(issue, repository); |
|
|
|
|
|
const jobId = await this.createJob(accountLink.huggingfaceUsername, jobParams); |
|
|
|
if (jobId) { |
|
console.log(`β
Created HugEx job ${jobId} for issue #${issue.number}`); |
|
|
|
|
|
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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
private async extractJobParamsFromIssue(issue: any, repository: any): Promise<HugExJobCreationParams> { |
|
|
|
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: {} |
|
}; |
|
|
|
|
|
try { |
|
|
|
const jsonMatch = issue.body.match(/```json\s*([\s\S]*?)\s*```/); |
|
if (jsonMatch && jsonMatch[1]) { |
|
const customParams = JSON.parse(jsonMatch[1]); |
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
return params; |
|
} |
|
|
|
|
|
|
|
|
|
async createJob(huggingfaceUsername: string, params: HugExJobCreationParams): Promise<string | null> { |
|
try { |
|
|
|
|
|
|
|
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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
private async commentOnIssue(owner: string, repo: string, issueNumber: number, body: string): Promise<void> { |
|
try { |
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
private async getHuggingFaceToken(username: string): Promise<string | null> { |
|
|
|
const accountLink = accountLinkingService.findByHuggingFaceUser(username); |
|
|
|
if (accountLink && accountLink.huggingfaceAccessToken) { |
|
console.log(`β
Retrieved HuggingFace access token for user: ${username}`); |
|
return accountLink.huggingfaceAccessToken; |
|
} |
|
|
|
|
|
console.warn(`β οΈ No HuggingFace access token found for user: ${username}. Using default token if available.`); |
|
return process.env.HF_DEFAULT_TOKEN || null; |
|
} |
|
} |
|
|
|
|
|
export const hugexService = new HugExService(); |