jules / services /templateService.ts
GraziePrego's picture
Upload folder using huggingface_hub
34450be verified
export interface GithubTemplate {
name: string;
path: string;
content: string;
download_url: string;
variables?: string[];
variableMetadata?: Record<string, string>;
}
export class TemplateService {
private static REPO_NAME = 'agent-notes';
/**
* Fetches template files from a GitHub repository.
* @param profile The GitHub username/org (used for token fallback)
* @param token The GitHub Personal Access Token (optional but recommended)
* @param owner The owner of the repo to fetch from
* @param repo The name of the repo to fetch from
* @param path The subfolder path to fetch from
*/
async fetchTemplates(
profile: string,
token?: string,
owner?: string,
repo?: string,
path?: string,
branch?: string
): Promise<GithubTemplate[]> {
if (!profile) return [];
const targetOwner = owner || profile;
const targetRepo = repo || TemplateService.REPO_NAME;
const targetPath = path || '';
const endpoint = `/api/github/contents?owner=${targetOwner}&repo=${targetRepo}&path=${targetPath}${token ? '&token=' + token : ''}&profile=${profile}${branch ? '&ref=' + branch : ''}`;
try {
const response = await fetch(endpoint);
if (!response.ok) {
if (response.status === 404) {
console.warn(`Template repository not found for ${profile}`);
return [];
}
throw new Error(`GitHub API error: ${response.status}`);
}
const files = await response.json();
console.log(`[TemplateService] Received ${Array.isArray(files) ? files.length : 'non-array'} entries from GitHub`);
if (!Array.isArray(files)) {
console.warn("[TemplateService] GitHub response is not an array:", files);
return [];
}
// Filter for markdown and text files
const mdFiles = files.filter((file: any) =>
file.type === 'file' && (file.name.endsWith('.md') || file.name.endsWith('.txt'))
);
console.log(`[TemplateService] Found ${mdFiles.length} markdown/text files`);
// Fetch content for each file
const templates = await Promise.all(mdFiles.map(async (file: any) => {
const rawUrl = `/api/github/raw?url=${encodeURIComponent(file.download_url)}${token ? '&token=' + token : ''}&profile=${profile}${branch ? '&ref=' + branch : ''}`;
const contentResponse = await fetch(rawUrl);
const content = await contentResponse.text();
// Parse for new JSON structure
const { cleanContent, variables, variableMetadata } = this.parseTemplateJson(content);
return {
name: file.name.replace('.md', '').replace('.txt', ''),
path: file.path,
content: cleanContent,
download_url: file.download_url,
variables,
variableMetadata
};
}));
return templates;
} catch (error) {
console.error("Failed to fetch GitHub templates:", error);
return [];
}
}
/**
* Detects and parses a JSON block at the start of the template.
* Format:
* {
* "variables": { "KEY": "Description" }
* }
*/
private parseTemplateJson(content: string): {
cleanContent: string,
variables?: string[],
variableMetadata?: Record<string, string>
} {
// Look for JSON at the very beginning of the string
const jsonMatch = content.match(/^\s*(\{[\s\S]*?\})(\s*[\r\n]+|$)/);
if (jsonMatch) {
try {
const json = JSON.parse(jsonMatch[1]);
if (json.variables) {
const variables = Object.keys(json.variables);
const variableMetadata = json.variables;
// Content starts after the JSON block
const cleanContent = content.substring(jsonMatch[0].length).trim();
return { cleanContent, variables, variableMetadata };
}
} catch (e) {
// Not valid JSON or doesn't follow structure, return original
console.warn("Found potential JSON header but failed to parse it as template metadata.", e);
}
}
return { cleanContent: content, variables: undefined };
}
}