|
|
""" |
|
|
Service adapters for Claude AI content generation |
|
|
""" |
|
|
|
|
|
import os |
|
|
from typing import Optional |
|
|
import anthropic |
|
|
|
|
|
|
|
|
class RepoProfile: |
|
|
def __init__( |
|
|
self, |
|
|
url: str, |
|
|
owner: str, |
|
|
name: str, |
|
|
description: str, |
|
|
languages: dict, |
|
|
primary_language: str, |
|
|
stars: int, |
|
|
forks: int, |
|
|
structure: dict, |
|
|
readme: str, |
|
|
main_features: list[str], |
|
|
technical_stack: list[str], |
|
|
quickstart_path: Optional[str] = None, |
|
|
commits: Optional[list] = None, |
|
|
commits_with_diffs: Optional[list] = None, |
|
|
breaking_changes: Optional[list] = None, |
|
|
initial_readme: Optional[str] = None, |
|
|
readme_diff: Optional[str] = None, |
|
|
has_readme_changes: bool = False, |
|
|
): |
|
|
self.url = url |
|
|
self.owner = owner |
|
|
self.name = name |
|
|
self.description = description |
|
|
self.languages = languages |
|
|
self.primary_language = primary_language |
|
|
self.stars = stars |
|
|
self.forks = forks |
|
|
self.structure = structure |
|
|
self.readme = readme |
|
|
self.main_features = main_features |
|
|
self.technical_stack = technical_stack |
|
|
self.quickstart_path = quickstart_path |
|
|
self.commits = commits or [] |
|
|
self.commits_with_diffs = commits_with_diffs or [] |
|
|
self.breaking_changes = breaking_changes or [] |
|
|
self.initial_readme = initial_readme or "" |
|
|
self.readme_diff = readme_diff or "" |
|
|
self.has_readme_changes = has_readme_changes |
|
|
|
|
|
|
|
|
class ContentVariant: |
|
|
def __init__( |
|
|
self, |
|
|
id: str, |
|
|
format: str, |
|
|
content: str, |
|
|
score: Optional[float] = None, |
|
|
evaluation_metrics: Optional[dict] = None, |
|
|
): |
|
|
self.id = id |
|
|
self.format = format |
|
|
self.content = content |
|
|
self.score = score |
|
|
self.evaluation_metrics = evaluation_metrics |
|
|
|
|
|
|
|
|
|
|
|
class ClaudeService: |
|
|
def __init__(self, api_key: Optional[str] = None): |
|
|
"""Initialize Claude service. |
|
|
|
|
|
Priority for API key: |
|
|
1. Environment variable ANTHROPIC_API_KEY (for Hugging Face Spaces secrets) |
|
|
2. User-provided api_key parameter (for manual override) |
|
|
""" |
|
|
|
|
|
self.api_key = os.environ.get("ANTHROPIC_API_KEY") or api_key |
|
|
if not self.api_key: |
|
|
raise ValueError("ANTHROPIC_API_KEY not configured. Please provide your own API key in Settings.") |
|
|
self.client = anthropic.Anthropic(api_key=self.api_key) |
|
|
|
|
|
def extract_repo_profile(self, repo_data: dict) -> dict: |
|
|
prompt = f"""Analyze this GitHub repository and extract a structured profile: |
|
|
|
|
|
Repository: {repo_data.get('full_name', '')} |
|
|
Description: {repo_data.get('description', '')} |
|
|
Languages: {repo_data.get('languages', {})} |
|
|
README: {repo_data.get('readme', '')[:3000]} |
|
|
|
|
|
Extract: |
|
|
1. Main features (3-5 key capabilities) |
|
|
2. Technical stack (frameworks, tools, dependencies) |
|
|
3. Primary use cases |
|
|
4. Key directories and their purposes |
|
|
5. Quickstart path if available |
|
|
|
|
|
Return as JSON with this structure: |
|
|
{{ |
|
|
"mainFeatures": string[], |
|
|
"technicalStack": string[], |
|
|
"useCases": string[], |
|
|
"keyDirectories": string[], |
|
|
"quickstartPath": string | null |
|
|
}}""" |
|
|
|
|
|
try: |
|
|
response = self.client.messages.create( |
|
|
model="claude-sonnet-4-20250514", |
|
|
max_tokens=2048, |
|
|
messages=[{"role": "user", "content": prompt}], |
|
|
) |
|
|
import json |
|
|
|
|
|
text = response.content[0].text |
|
|
|
|
|
try: |
|
|
return json.loads(text) |
|
|
except json.JSONDecodeError: |
|
|
|
|
|
import re |
|
|
|
|
|
json_match = re.search(r"\{[\s\S]*\}", text) |
|
|
if json_match: |
|
|
return json.loads(json_match.group()) |
|
|
return self._extract_profile_fallback(repo_data) |
|
|
except Exception as e: |
|
|
print(f"Claude API error: {e}") |
|
|
return self._extract_profile_fallback(repo_data) |
|
|
|
|
|
def _extract_profile_fallback(self, repo_data: dict) -> dict: |
|
|
return { |
|
|
"mainFeatures": [ |
|
|
repo_data.get("description", "No description available"), |
|
|
"See README for details", |
|
|
], |
|
|
"technicalStack": list(repo_data.get("languages", {}).keys())[:5], |
|
|
"useCases": ["General development"], |
|
|
"keyDirectories": repo_data.get("structure", {}).get("keyDirectories", []), |
|
|
"quickstartPath": None, |
|
|
} |
|
|
|
|
|
def generate_content_variants( |
|
|
self, profile: RepoProfile, format: str, variant_count: int = 2 |
|
|
) -> list[ContentVariant]: |
|
|
prompt = self._get_prompt_for_format(format, profile) |
|
|
variants = [] |
|
|
|
|
|
try: |
|
|
for i in range(variant_count): |
|
|
response = self.client.messages.create( |
|
|
model="claude-sonnet-4-20250514", |
|
|
max_tokens=4096, |
|
|
temperature=0.7 + (i * 0.1), |
|
|
messages=[{"role": "user", "content": prompt}], |
|
|
) |
|
|
content = response.content[0].text |
|
|
variants.append( |
|
|
ContentVariant( |
|
|
id=f"{format}-variant-{i + 1}", |
|
|
format=format, |
|
|
content=content, |
|
|
) |
|
|
) |
|
|
except Exception as e: |
|
|
print(f"Claude generation error: {e}") |
|
|
variants.append( |
|
|
ContentVariant( |
|
|
id=f"{format}-variant-1", |
|
|
format=format, |
|
|
content=self._generate_fallback_content(format, profile), |
|
|
) |
|
|
) |
|
|
|
|
|
return variants |
|
|
|
|
|
def _generate_fallback_content(self, format: str, profile: RepoProfile) -> str: |
|
|
repo_name = profile.name |
|
|
description = profile.description |
|
|
|
|
|
if format == "tutorial": |
|
|
return f"""# Getting Started with {repo_name} |
|
|
|
|
|
## Overview |
|
|
{description} |
|
|
|
|
|
## Prerequisites |
|
|
- Git installed |
|
|
- Basic knowledge of {profile.primary_language} |
|
|
|
|
|
## Installation |
|
|
```bash |
|
|
git clone {profile.url} |
|
|
cd {repo_name} |
|
|
# Follow project-specific setup instructions |
|
|
``` |
|
|
|
|
|
## Next Steps |
|
|
Check the README for detailed documentation. |
|
|
|
|
|
*Note: This is a fallback tutorial. For AI-generated content, please add valid ANTHROPIC_API_KEY.*""" |
|
|
|
|
|
elif format == "blog": |
|
|
features = "\n".join([f"- {f}" for f in profile.main_features]) |
|
|
return f"""# Introducing {repo_name} |
|
|
|
|
|
{description} |
|
|
|
|
|
## Key Features |
|
|
{features} |
|
|
|
|
|
## Tech Stack |
|
|
Built with {', '.join(profile.technical_stack)} |
|
|
|
|
|
## Get Started |
|
|
Visit the repository: {profile.url} |
|
|
|
|
|
*Note: This is a fallback blog post. For AI-generated content, please add valid ANTHROPIC_API_KEY.*""" |
|
|
|
|
|
elif format == "talk": |
|
|
return f"""# {repo_name}: Conference Talk Outline |
|
|
|
|
|
## Title |
|
|
"Introduction to {repo_name}" |
|
|
|
|
|
## Abstract |
|
|
{description} |
|
|
|
|
|
## Slides (30-45 minutes) |
|
|
1. Introduction & Background |
|
|
2. Problem Statement |
|
|
3. Solution Overview |
|
|
4. Key Features |
|
|
5. Live Demo |
|
|
6. Q&A |
|
|
|
|
|
*Note: This is a fallback talk outline. For AI-generated content, please add valid ANTHROPIC_API_KEY.*""" |
|
|
|
|
|
elif format == "twitter": |
|
|
return f"""π Check out {repo_name}! |
|
|
|
|
|
{description} |
|
|
|
|
|
Built with {profile.primary_language} |
|
|
β {profile.stars} stars |
|
|
|
|
|
Learn more: {profile.url} |
|
|
|
|
|
#OpenSource #{profile.primary_language} |
|
|
|
|
|
*Note: This is a fallback tweet. For AI-generated content, please add valid ANTHROPIC_API_KEY.*""" |
|
|
|
|
|
elif format == "linkedin": |
|
|
features = "\n".join([f"β’ {f}" for f in profile.main_features]) |
|
|
return f"""I'm excited to share {repo_name} with the community. |
|
|
|
|
|
{description} |
|
|
|
|
|
Key highlights: |
|
|
{features} |
|
|
|
|
|
Check it out: {profile.url} |
|
|
|
|
|
*Note: This is a fallback LinkedIn post. For AI-generated content, please add valid ANTHROPIC_API_KEY.*""" |
|
|
|
|
|
elif format == "hackathon": |
|
|
return f"""# Hackathon Challenge: Build with {repo_name} |
|
|
|
|
|
## Challenge Description |
|
|
Create an innovative project using {repo_name} |
|
|
|
|
|
## Requirements |
|
|
- Use {profile.primary_language} |
|
|
- Implement at least one key feature |
|
|
- Document your approach |
|
|
|
|
|
## Judging Criteria |
|
|
- Innovation |
|
|
- Technical implementation |
|
|
- Code quality |
|
|
- Presentation |
|
|
|
|
|
*Note: This is a fallback challenge. For AI-generated content, please add valid ANTHROPIC_API_KEY.*""" |
|
|
|
|
|
return f"Content for {format} format.\n\n*Note: Add ANTHROPIC_API_KEY for AI-generated content.*" |
|
|
|
|
|
def _get_prompt_for_format(self, format: str, profile: RepoProfile) -> str: |
|
|
commits_list = profile.commits[:5] if profile.commits else [] |
|
|
recent_commits = "" |
|
|
if commits_list: |
|
|
commits_text = "\n".join( |
|
|
[ |
|
|
f"- {c.get('date', '')[:10]}: {c.get('message', '').split(chr(10))[0]}" |
|
|
for c in commits_list |
|
|
] |
|
|
) |
|
|
recent_commits = f"\nRecent Commits (most recent first):\n{commits_text}" |
|
|
|
|
|
|
|
|
code_diffs = "" |
|
|
if profile.commits_with_diffs: |
|
|
diff_summaries = [] |
|
|
for commit in profile.commits_with_diffs[:3]: |
|
|
diff = commit.get("diff", "") |
|
|
if diff: |
|
|
|
|
|
diff_preview = diff[:2000] + "..." if len(diff) > 2000 else diff |
|
|
diff_summaries.append(f"### {commit.get('message', 'No message')}\n```diff\n{diff_preview}\n```") |
|
|
if diff_summaries: |
|
|
code_diffs = f"\n\nCode Changes (diffs from recent commits):\n" + "\n\n".join(diff_summaries) |
|
|
|
|
|
|
|
|
breaking_changes_ctx = "" |
|
|
if profile.breaking_changes: |
|
|
bc_items = [] |
|
|
for bc in profile.breaking_changes[:5]: |
|
|
changes_desc = ", ".join([f"{c['type']} ({c['count']})" for c in bc.get("changes", [])]) |
|
|
bc_items.append(f"- [{bc.get('sha', '')}] {bc.get('message', '')}: {changes_desc}") |
|
|
if bc_items: |
|
|
breaking_changes_ctx = f"\n\nβ οΈ POTENTIAL BREAKING CHANGES DETECTED:\n" + "\n".join(bc_items) |
|
|
breaking_changes_ctx += "\nIMPORTANT: If breaking changes exist, you MUST include migration guidance in the content." |
|
|
|
|
|
|
|
|
readme_changes_ctx = "" |
|
|
if profile.has_readme_changes and profile.readme_diff: |
|
|
readme_changes_ctx = f"\n\nπ README CHANGES DETECTED (HIGHEST PRIORITY):\nThe README documentation has been updated during the analysis period. These changes likely reflect important API changes, new features, or usage updates that MUST be prominently featured:\n\n{profile.readme_diff[:3000]}" |
|
|
|
|
|
|
|
|
initial_readme_ctx = "" |
|
|
if profile.initial_readme: |
|
|
initial_readme_ctx = f"\n\nProject Background (from initial README):\n{profile.initial_readme[:2000]}" |
|
|
|
|
|
base_context = f"""Repository: {profile.name} |
|
|
Description: {profile.description} |
|
|
Main Features: {', '.join(profile.main_features)} |
|
|
Tech Stack: {', '.join(profile.technical_stack)} |
|
|
Primary Language: {profile.primary_language}{recent_commits}{code_diffs}{breaking_changes_ctx}{readme_changes_ctx}{initial_readme_ctx} |
|
|
Note: Focus content on changes within the selected analysis period when relevant.""" |
|
|
|
|
|
prompts = { |
|
|
"tutorial": f"""{base_context} |
|
|
|
|
|
Create a step-by-step tutorial showcasing the LATEST updates and new features from recent commits. |
|
|
|
|
|
IMPORTANT: |
|
|
- Start with a brief 2-3 sentence project introduction |
|
|
- Focus 80% of content on recent updates, new features, and changes from the commit history |
|
|
- Highlight what's NEW and DIFFERENT compared to older versions |
|
|
- Include working code examples demonstrating recent features |
|
|
- If no recent commits, focus on the most advanced/latest capabilities |
|
|
|
|
|
Include: |
|
|
- Brief project overview (2-3 sentences) |
|
|
- What's new in recent updates (primary focus) |
|
|
- Step-by-step guide to using new features |
|
|
- Code examples showcasing latest additions |
|
|
- Migration tips if APIs changed |
|
|
- Next steps with new features |
|
|
|
|
|
Make it executable and accurate. Use markdown format.""", |
|
|
"blog": f"""{base_context} |
|
|
|
|
|
Write a technical blog post announcing the LATEST updates and new features. |
|
|
|
|
|
IMPORTANT: |
|
|
- Focus on recent changes and what's NEW (based on recent commits) |
|
|
- Brief context about the project (1 paragraph max) |
|
|
- Deep dive into latest features and improvements (70-80% of content) |
|
|
- Show before/after comparisons if APIs changed |
|
|
- Highlight breaking changes or major improvements |
|
|
|
|
|
Include: |
|
|
- Hook: What's new and why it matters NOW |
|
|
- Brief project context (1 paragraph) |
|
|
- Deep dive: Latest features and updates (primary focus) |
|
|
- Code examples showcasing new capabilities |
|
|
- Migration guide if needed |
|
|
- What's coming next |
|
|
- Call to action |
|
|
|
|
|
Target audience: Technical developers. Use markdown format.""", |
|
|
"talk": f"""{base_context} |
|
|
|
|
|
Create a 30-45 minute meetup/conference talk outline focused on LATEST updates and new features. |
|
|
|
|
|
IMPORTANT: |
|
|
- This is for a meetup/conference - focus on what's NEW and exciting |
|
|
- Brief intro (2-3 slides max), then dive into recent updates |
|
|
- 70-80% of talk should cover latest features, improvements, and changes |
|
|
- Include live demos of new capabilities |
|
|
- Show real code from recent commits |
|
|
|
|
|
Include: |
|
|
- Talk title emphasizing "What's New" or "Latest Updates" |
|
|
- Abstract highlighting recent changes (150 words) |
|
|
- Slide structure (15-20 slides): |
|
|
* Quick intro (2-3 slides) |
|
|
* Recent updates deep dive (10-12 slides) |
|
|
* Live demos of new features (3-4 slides) |
|
|
* Roadmap and what's next (1-2 slides) |
|
|
- Speaker notes focusing on recent changes |
|
|
- Demo points showcasing latest features |
|
|
- Q&A prep about new capabilities |
|
|
|
|
|
Format as markdown with clear sections.""", |
|
|
"twitter": f"""{base_context} |
|
|
|
|
|
Write an engaging Twitter/X thread (5-7 tweets) announcing the LATEST updates. |
|
|
|
|
|
IMPORTANT: |
|
|
- Focus on what's NEW (recent commits/features) |
|
|
- First tweet: Brief intro + "Here's what's new π§΅" |
|
|
- Next 3-5 tweets: Deep dive into specific new features |
|
|
- Last tweet: Call to action |
|
|
|
|
|
Make it: |
|
|
- Start with "What's new in [project]" hook |
|
|
- Highlight recent features/changes (use commit history) |
|
|
- Technical but accessible |
|
|
- Add relevant emojis (π β‘ π β¨) |
|
|
- Include code snippets for new features |
|
|
- End with link and CTA |
|
|
- Each tweet under 280 chars |
|
|
|
|
|
Format as numbered thread.""", |
|
|
"linkedin": f"""{base_context} |
|
|
|
|
|
Write a LinkedIn post announcing LATEST updates for a technical leadership audience. |
|
|
|
|
|
IMPORTANT: |
|
|
- Focus on recent updates and what's new |
|
|
- Frame updates in terms of business value and impact |
|
|
- Brief context, then dive into latest improvements |
|
|
|
|
|
Include: |
|
|
- Hook: Major update/new release announcement |
|
|
- Brief project context (1-2 sentences) |
|
|
- What's new: Recent features and improvements (primary focus) |
|
|
- Business value of new updates |
|
|
- Technical highlights of latest additions |
|
|
- Team/community impact |
|
|
- Call to action |
|
|
|
|
|
Tone: Professional, insightful, forward-looking. 200-300 words.""", |
|
|
"hackathon": f"""{base_context} |
|
|
|
|
|
Design a hackathon challenge focused on exploring and using the LATEST features. |
|
|
|
|
|
IMPORTANT: |
|
|
- Challenge should require using recent updates/new features |
|
|
- Encourage participants to push boundaries of new capabilities |
|
|
- Provide recent code examples as starting points |
|
|
|
|
|
Include: |
|
|
- Challenge title emphasizing "Latest Features" or "New Capabilities" |
|
|
- Brief project intro (2-3 sentences) |
|
|
- Problem statement leveraging new features |
|
|
- Required deliverables (must use recent updates) |
|
|
- Judging criteria (4-5 points, bonus for creative use of new features) |
|
|
- Starter resources with recent code examples |
|
|
- Sample tasks exploring latest additions (3-5) |
|
|
- Difficulty level |
|
|
|
|
|
Make it engaging and achievable in 24-48 hours.""", |
|
|
} |
|
|
|
|
|
return prompts.get( |
|
|
format, f"{base_context}\n\nCreate content for {format} format." |
|
|
) |
|
|
|
|
|
|