Spaces:
Paused
Paused
| 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 }; | |
| } | |
| } | |