Skip to content

Commit 27627b7

Browse files
authored
Merge pull request #119 from rasulkireev/gtpr
Use gtpr for blog posts.
2 parents 9083de9 + 0ecf075 commit 27627b7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+5507
-1704
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ REDIS_PASSWORD=tuxseo
2929

3030
### Required ####
3131

32+
# https://platform.openai.com/api-keys
33+
OPENAI_API_KEY=
34+
35+
# https://tavily.com/
36+
TAVILY_API_KEY=
37+
3238
# https://jina.ai/reader/
3339
JINA_READER_API_KEY=
3440

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
1414
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1515

1616

17+
## [0.0.8] - 2025-11-23
18+
### Added
19+
- Added link exchange program for paid users
20+
21+
### Changed
22+
- using gtpr for writing posts
23+
24+
### Fixed
25+
- correct link on project scan
26+
1727
## [0.0.8] - 2025-11-12
1828
### Changed
1929
- Navbar spacing rules

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/rasulkireev/tuxseo)
4040

4141
The only required env vars are:
42+
- OPENAI_API_KEY
43+
- TAVILY_API_KEY
4244
- GEMINI_API_KEY
4345
- PERPLEXITY_API_KEY
4446
- JINA_READER_API_KEY

core/agents/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from core.agents.competitor_vs_blog_post_agent import (
44
create_competitor_vs_blog_post_agent,
55
)
6-
from core.agents.content_editor_agent import create_content_editor_agent
76
from core.agents.extract_competitors_data_agent import (
87
create_extract_competitors_data_agent,
98
)
@@ -12,26 +11,23 @@
1211
from core.agents.generate_blog_post_content_agent import (
1312
create_generate_blog_post_content_agent,
1413
)
14+
from core.agents.insert_links_agent import create_insert_links_agent
1515
from core.agents.populate_competitor_details_agent import (
1616
create_populate_competitor_details_agent,
1717
)
1818
from core.agents.summarize_page_agent import create_summarize_page_agent
1919
from core.agents.title_suggestions_agent import create_title_suggestions_agent
20-
from core.agents.validate_blog_post_ending_agent import (
21-
create_validate_blog_post_ending_agent,
22-
)
2320

2421
__all__ = [
2522
"create_analyze_competitor_agent",
2623
"create_analyze_project_agent",
2724
"create_competitor_vs_blog_post_agent",
28-
"create_content_editor_agent",
2925
"create_extract_competitors_data_agent",
3026
"create_extract_links_agent",
3127
"create_find_competitors_agent",
3228
"create_generate_blog_post_content_agent",
29+
"create_insert_links_agent",
3330
"create_populate_competitor_details_agent",
3431
"create_summarize_page_agent",
3532
"create_title_suggestions_agent",
36-
"create_validate_blog_post_ending_agent",
3733
]

core/agents/content_editor_agent.py

Lines changed: 0 additions & 49 deletions
This file was deleted.

core/agents/insert_links_agent.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from pydantic_ai import Agent
2+
3+
from core.agents.schemas import LinkInsertionContext
4+
from core.choices import get_default_ai_model
5+
6+
7+
def create_insert_links_agent(model=None):
8+
"""
9+
Create an agent to insert links into blog post content organically.
10+
11+
Args:
12+
model: Optional AI model to use. Defaults to GPT-4o.
13+
14+
Returns:
15+
Configured Agent instance
16+
"""
17+
if model is None:
18+
model = get_default_ai_model()
19+
20+
agent = Agent(
21+
model,
22+
output_type=str,
23+
deps_type=LinkInsertionContext,
24+
system_prompt="""
25+
You are an expert content editor specializing in organic link insertion.
26+
27+
Your task is to insert links from the provided project pages into the blog post content.
28+
The links should be inserted naturally and organically where they add value to the reader.
29+
30+
CRITICAL RULES:
31+
1. DO NOT edit, rewrite, or change ANY of the existing blog post content
32+
2. DO NOT add new paragraphs or sentences
33+
3. DO NOT remove any existing content
34+
4. ONLY insert markdown links into existing text where appropriate
35+
5. Insert links by wrapping relevant existing text with markdown link syntax: [text](url)
36+
6. Each project page should be linked AT MOST ONCE in the entire post
37+
7. Only insert links where they are contextually relevant and add value
38+
8. Return the EXACT same blog post with only the links inserted
39+
9. Return ONLY the markdown content, no JSON or structured output
40+
41+
HANDLING EXISTING REFERENCE LINKS:
42+
- If the blog post contains reference-style links like "text [Source, Year](link)" or "text [1](link)"
43+
- Convert them to inline links by integrating them naturally into the text
44+
- Example: "This is a fact [Source, 2024](https://example.com)" → "This is [a fact](https://example.com)"
45+
- Example: "According to research [1](https://example.com)" → "According to [research](https://example.com)"
46+
- DO NOT keep the reference format like [Source, Year] or [1], [2], etc.
47+
- Make the links flow naturally within the sentence
48+
49+
Link Insertion Guidelines:
50+
- Look for phrases or sentences where linking would naturally enhance the content
51+
- Link relevant keywords or phrases that relate to the project page content
52+
- Ensure the anchor text is descriptive and natural
53+
- Distribute links throughout the post (not all in one section)
54+
- Prefer linking in body paragraphs over headers or introduction
55+
56+
Example of what TO DO:
57+
Original: "This feature helps you track your website performance."
58+
With Link: "This feature helps you [track your website performance](https://example.com/analytics)."
59+
60+
Converting Reference Links:
61+
Original: "Machine learning improves accuracy [Study, 2024](https://example.com)."
62+
Converted: "Machine learning [improves accuracy](https://example.com)."
63+
64+
Example of what NOT TO DO:
65+
- DO NOT add: "Learn more about our features [here](url)" if that sentence wasn't there
66+
- DO NOT change: "This is great" to "This is amazing" even with a link
67+
- DO NOT restructure or reformat the content
68+
- DO NOT keep reference-style citations like [Source, Year](url) or [1](url)
69+
""", # noqa: E501
70+
retries=2,
71+
model_settings={"max_tokens": 65500, "temperature": 0.1},
72+
)
73+
74+
@agent.system_prompt
75+
def add_link_insertion_context(ctx) -> str:
76+
context: LinkInsertionContext = ctx.deps
77+
78+
pages_info = ""
79+
for index, page in enumerate(context.project_pages, start=1):
80+
pages_info += f"""
81+
Page {index}:
82+
- URL: {page.url}
83+
- Title: {page.title}
84+
- Description: {page.description}
85+
- Summary: {page.summary}
86+
"""
87+
88+
return f"""
89+
PROJECT PAGES TO LINK:
90+
{pages_info}
91+
92+
BLOG POST CONTENT TO INSERT LINKS INTO:
93+
{context.blog_post_content}
94+
95+
INSTRUCTIONS:
96+
1. Insert links from the project pages above organically into the blog post
97+
2. Convert any existing reference-style links (like "text [Source, Year](url)" or "text [1](url)") to natural inline links (like "[text](url)")
98+
3. Do not change the content, only insert/convert links
99+
4. Return only the markdown content with links properly inserted
100+
""" # noqa: E501
101+
102+
return agent

core/agents/schemas.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,12 @@ class CompetitorVsPostContext(BaseModel):
275275
project_pages: list[ProjectPageContext] = Field(
276276
default_factory=list, description="List of project pages available for linking"
277277
)
278+
279+
280+
class LinkInsertionContext(BaseModel):
281+
"""Context for inserting links into blog post content."""
282+
283+
blog_post_content: str = Field(description="The blog post content in markdown format")
284+
project_pages: list[ProjectPageContext] = Field(
285+
default_factory=list, description="List of project pages available for linking"
286+
)

core/agents/validate_blog_post_ending_agent.py

Lines changed: 0 additions & 41 deletions
This file was deleted.

core/api/schemas.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ class ToggleOGImageGenerationOut(Schema):
231231
message: str = ""
232232

233233

234+
class ToggleLinkExchangeOut(Schema):
235+
status: str
236+
enabled: bool
237+
message: str = ""
238+
239+
234240
class FixGeneratedBlogPostIn(Schema):
235241
id: int
236242

core/api/views.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
SubmitFeedbackIn,
3838
SubmitSitemapIn,
3939
ToggleAutoSubmissionOut,
40+
ToggleLinkExchangeOut,
4041
ToggleOGImageGenerationOut,
4142
ToggleProjectKeywordUseIn,
4243
ToggleProjectKeywordUseOut,
@@ -452,6 +453,13 @@ def toggle_auto_submission(request: HttpRequest, project_id: int):
452453
profile = request.auth
453454
project = get_object_or_404(Project, id=project_id, profile=profile)
454455

456+
if not profile.is_on_pro_plan:
457+
return {
458+
"status": "error",
459+
"enabled": False,
460+
"message": "Automatic Post Submission is only available on the Pro plan. Please upgrade to access this feature.", # noqa: E501
461+
}
462+
455463
project.enable_automatic_post_submission = not project.enable_automatic_post_submission
456464
project.save(update_fields=["enable_automatic_post_submission"])
457465

@@ -467,12 +475,41 @@ def toggle_og_image_generation(request: HttpRequest, project_id: int):
467475
profile = request.auth
468476
project = get_object_or_404(Project, id=project_id, profile=profile)
469477

478+
if not profile.is_on_pro_plan:
479+
return {
480+
"status": "error",
481+
"enabled": False,
482+
"message": "OG Image Generation is only available on the Pro plan. Please upgrade to access this feature.", # noqa: E501
483+
}
484+
470485
project.enable_automatic_og_image_generation = not project.enable_automatic_og_image_generation
471486
project.save(update_fields=["enable_automatic_og_image_generation"])
472487

473488
return {"status": "success", "enabled": project.enable_automatic_og_image_generation}
474489

475490

491+
@api.post(
492+
"/projects/{project_id}/toggle-link-exchange",
493+
response=ToggleLinkExchangeOut,
494+
auth=[session_auth],
495+
)
496+
def toggle_link_exchange(request: HttpRequest, project_id: int):
497+
profile = request.auth
498+
project = get_object_or_404(Project, id=project_id, profile=profile)
499+
500+
if not profile.is_on_pro_plan:
501+
return {
502+
"status": "error",
503+
"enabled": False,
504+
"message": "Link Exchange is only available on the Pro plan. Please upgrade to access this feature.", # noqa: E501
505+
}
506+
507+
project.particiate_in_link_exchange = not project.particiate_in_link_exchange
508+
project.save(update_fields=["particiate_in_link_exchange"])
509+
510+
return {"status": "success", "enabled": project.particiate_in_link_exchange}
511+
512+
476513
@api.post("/projects/update-sitemap-url", response=UpdateSitemapUrlOut, auth=[session_auth])
477514
def update_sitemap_url(request: HttpRequest, data: UpdateSitemapUrlIn):
478515
"""
@@ -1090,13 +1127,6 @@ def fix_generated_blog_post(request: HttpRequest, data: FixGeneratedBlogPostIn):
10901127
if generated_post.project and generated_post.project.profile != profile:
10911128
return {"status": "error", "message": "Forbidden: You do not have access to this post."}
10921129

1093-
# Check if there are actually issues to fix
1094-
if generated_post.blog_post_content_is_valid:
1095-
return {"status": "success", "message": "Blog post content is already valid."}
1096-
1097-
# Run the fix method
1098-
generated_post.fix_generated_blog_post()
1099-
11001130
return {"status": "success", "message": "Blog post issues have been fixed successfully."}
11011131

11021132
except GeneratedBlogPost.DoesNotExist:

0 commit comments

Comments
 (0)