Skip to content

Add inspiration tab and integrate into generation#106

Open
rasulkireev wants to merge 1 commit intomainfrom
cursor/add-inspiration-tab-and-integrate-into-generation-ed6b
Open

Add inspiration tab and integrate into generation#106
rasulkireev wants to merge 1 commit intomainfrom
cursor/add-inspiration-tab-and-integrate-into-generation-ed6b

Conversation

@rasulkireev
Copy link
Owner

@rasulkireev rasulkireev commented Nov 3, 2025

This pull request contains changes generated by a Cursor Cloud Agent

Open in Cursor Open in Web

Summary by CodeRabbit

  • New Features
    • Users can now save and manage inspirations to guide blog post generation toward preferred styles and structures
    • Inspirations are integrated into the blog generation process to influence content creation
    • New Inspirations section in project navigation with full add/delete management
    • Added admin support for inspiration management

This commit introduces a new feature that allows users to add and manage inspiration sources for their blog posts. This includes:

- A new `Inspiration` model to store inspiration data.
- API endpoints for adding and deleting inspirations.
- Frontend components and controllers for managing inspirations.
- Integration with the agent system to use inspirations for content generation.

Co-authored-by: me <me@rasulkireev.com>
@cursor
Copy link

cursor bot commented Nov 3, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

Introduces an inspirations feature enabling users to add source materials (URLs with titles and descriptions) that influence blog post title and content generation. Includes a database model, API endpoints, system prompts, backend views, and a frontend Stimulus controller for managing inspirations.

Changes

Cohort / File(s) Summary
Database Model & Schema Definitions
core/models.py, core/schemas.py, core/admin.py
Adds Inspiration model with project FK, URL, title, description, and markdown content; includes get_page_content() helper. Defines InspirationContext schema. Updates TitleSuggestionContext and BlogPostGenerationContext with inspirations fields. Registers Inspiration in Django admin.
System Prompts & Agent Integration
core/agent_system_prompts.py, core/agents.py
Implements add_inspirations() system prompt function that formats inspirations into markdown blocks for generation workflows. Imports and applies to content_editor_agent system prompt chain.
API Layer
core/api/schemas.py, core/api/views.py
Adds AddInspirationIn/Out and DeleteInspirationIn/Out schemas. Implements two endpoints: POST /inspirations/add (validates URL, prevents duplicates, fetches page content) and POST /inspirations/delete (removes inspiration by ID).
Backend Views & Routing
core/views.py, core/urls.py
Adds ProjectInspirationsView for displaying project inspirations. Enhances GeneratedBlogPostDetailView with context enrichment for keywords and post metadata. Adds URL route for project inspirations.
Frontend Controller
frontend/src/controllers/inspiration_controller.js
Stimulus controller for inspiration management: toggleForm, addInspiration, deleteInspiration methods with CSRF token handling, form validation, loading indicators, and message feedback.
Frontend Templates
frontend/templates/base_project.html, frontend/templates/project/project_inspirations.html
Adds "Inspirations" navigation link in project sidebar. Creates project_inspirations template with add inspiration form, inspirations table (Title, URL, Added date, Delete), empty state, and total count display.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Controller as Inspiration<br/>Controller
    participant API as /api/<br/>Endpoints
    participant Service as Core<br/>Services
    participant DB as Database

    User->>Controller: Click "Add Inspiration"
    Controller->>Controller: toggleForm()
    
    User->>Controller: Enter URL & Submit
    Controller->>Controller: Validate URL
    Controller->>Controller: getCsrfToken()
    Controller->>API: POST /inspirations/add<br/>(project_id, url, csrf)
    API->>Service: Validate & Check Duplicate
    Service->>Service: Fetch Page Content
    Service->>DB: Create Inspiration Record
    DB-->>Service: Inspiration Details
    Service-->>API: Success + Details
    API-->>Controller: AddInspirationOut
    Controller->>Controller: showMessage("Added!")
    Controller->>Controller: Reload Page
    
    User->>Controller: Click Delete
    Controller->>User: Confirm Dialog
    User-->>Controller: Confirm
    Controller->>API: POST /inspirations/delete<br/>(inspiration_id, csrf)
    API->>Service: Find & Delete
    Service->>DB: Delete Inspiration
    DB-->>Service: Success
    Service-->>API: DeleteInspirationOut
    API-->>Controller: Success
    Controller->>Controller: Reload Page
Loading
sequenceDiagram
    autonumber
    participant User
    participant Generator as Content<br/>Generation
    participant Prompts as System<br/>Prompts
    participant LLM as AI<br/>Model

    User->>Generator: Request Title/Content
    Generator->>Prompts: Build Generation Context
    Prompts->>Prompts: add_inspirations()
    Prompts->>Prompts: Format inspirations block<br/>(titles, URLs, descriptions)
    Prompts-->>Generator: Enhanced Context
    Generator->>LLM: Send Prompt +<br/>Inspiration Sources
    LLM->>LLM: Generate with<br/>inspiration reference
    LLM-->>Generator: Generated Content
    Generator-->>User: Title / Blog Post
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas requiring extra attention:
    • core/models.py — Complex integration points: ensure get_page_content() properly handles fetch failures and markdown parsing; verify unique_together constraint on (project, url) is correctly enforced.
    • core/api/views.py — Validate URL sanitization and duplicate detection logic; confirm error handling and logging cover edge cases.
    • frontend/src/controllers/inspiration_controller.js — Review CSRF token retrieval, async error handling in addInspiration/deleteInspiration, and state management during loading.
    • core/views.py — Ensure GeneratedBlogPostDetailView queryset filtering by project_pk is secure and context enrichment logic is efficient.

Possibly related PRs

Poem

🐰 Inspirations gathered, woven into thought,
A tapestry of sources that the system sought.
From URLs scraped to prompts arranged,
Blog posts bloom with borrowed grace unchanged,
hops excitedly

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Add inspiration tab and integrate into generation" directly aligns with the primary changes in the pull request. The changeset introduces a new Inspiration model and creates a complete feature for managing inspirations: a new frontend tab/view (ProjectInspirationsView, inspiration_controller.js, and project_inspirations.html template), API endpoints for add/delete operations, and integration into the content generation pipeline (system prompts, contexts, and generation flows). The title concisely captures both major aspects—the user-facing inspiration management interface and the technical integration into the AI generation system—without being vague or overly broad.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/add-inspiration-tab-and-integrate-into-generation-ed6b

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rasulkireev rasulkireev marked this pull request as ready for review November 3, 2025 07:05
Copy link

@code-review-doctor code-review-doctor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth considering. View full project report here.

profile_id=profile.id,
)

return {"status": "success", "message": f"Inspiration deleted successfully"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return {"status": "success", "message": f"Inspiration deleted successfully"}
return {"status": "success", "message": "Inspiration deleted successfully"}

f-string is unnecessary here. This can just be a string. Read more.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Greptile Summary

Adds comprehensive inspiration feature allowing users to save blog posts/blogs as style references for AI-generated content.

Key Changes:

  • New Inspiration model with URL scraping via Jina Reader API to capture title, description, and markdown content
  • Integration of inspirations into AI agent context for both title generation and blog post content generation
  • Complete CRUD UI with Stimulus.js controller for managing inspirations
  • API endpoints for adding/deleting inspirations with proper validation and authorization
  • Inspirations automatically included in system prompts for content_editor_agent and title suggestion agents

Architecture:
The feature follows Django best practices with fat models (business logic in Inspiration.get_page_content()), API layer using django-ninja with Pydantic schemas, and clean separation between backend and frontend via Stimulus controllers.

Confidence Score: 4/5

  • Safe to merge with one performance consideration that should be addressed soon
  • The implementation is well-structured and follows project conventions. The main concern is the synchronous external API call in get_page_content() during the request-response cycle, which could cause timeout issues with slow or unresponsive URLs. This should ideally be moved to a background task using django-q2. The rest of the code is solid with proper validation, error handling, and clean integration into existing AI generation flows.
  • core/api/views.py line 1086 - synchronous external API call should be moved to background task

Important Files Changed

File Analysis

Filename Score Overview
core/models.py 4/5 Added Inspiration model and integrated inspirations into blog post generation context across multiple methods
core/api/views.py 5/5 Added API endpoints for adding and deleting inspirations with proper validation and error handling
core/agent_system_prompts.py 5/5 Added add_inspirations system prompt function to include inspiration sources in AI generation
frontend/src/controllers/inspiration_controller.js 4/5 Stimulus controller for managing inspirations with add/delete functionality and UI feedback

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant InspirationController
    participant API
    participant Project
    participant Inspiration
    participant JinaAPI
    participant Agent

    User->>Browser: Visit Inspirations Page
    Browser->>API: GET /project/{id}/inspirations/
    API->>Project: Load project and inspirations
    Project-->>Browser: Render inspirations list

    User->>InspirationController: Click "Add Inspiration"
    InspirationController->>Browser: Show form

    User->>InspirationController: Enter URL and submit
    InspirationController->>API: POST /api/inspirations/add
    API->>Project: Verify user owns project
    API->>Inspiration: Create new inspiration record
    API->>Inspiration: Call get_page_content()
    Inspiration->>JinaAPI: Fetch page content
    JinaAPI-->>Inspiration: Return title, description, markdown
    Inspiration->>Inspiration: Save scraped data
    Inspiration-->>API: Return success
    API-->>InspirationController: Return inspiration data
    InspirationController->>Browser: Reload page

    User->>Agent: Generate blog post titles
    Agent->>Project: Load inspirations
    Project->>Agent: Include inspiration context
    Agent->>Agent: Generate titles using inspiration style

    User->>Agent: Generate blog post content
    Agent->>Project: Load inspirations
    Project->>Agent: Include inspiration context
    Agent->>Agent: Generate content emulating inspiration style
Loading

12 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile


inspiration = Inspiration.objects.create(project=project, url=url_to_add)

inspiration.get_page_content()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: calling get_page_content() in the request-response cycle can cause timeout issues as it makes external HTTP requests. Consider moving this to a background task using django-q2

Suggested change
inspiration.get_page_content()
async_task("core.tasks.fetch_inspiration_content", inspiration.id)
Prompt To Fix With AI
This is a comment left during a code review.
Path: core/api/views.py
Line: 1086:1086

Comment:
**logic:** calling `get_page_content()` in the request-response cycle can cause timeout issues as it makes external HTTP requests. Consider moving this to a background task using django-q2

```suggestion
        async_task("core.tasks.fetch_inspiration_content", inspiration.id)
```

How can I resolve this? If you propose a fix, please make it concise.


export default class extends Controller {
static targets = ["form", "urlInput", "submitButton", "message", "tableBody", "emptyState"];
static values = { projectId: Number };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: emptyState target is declared but never used in the controller - the template references it but the controller doesn't interact with it

Suggested change
static values = { projectId: Number };
static targets = ["form", "urlInput", "submitButton", "message", "tableBody"];
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/controllers/inspiration_controller.js
Line: 5:5

Comment:
**style:** `emptyState` target is declared but never used in the controller - the template references it but the controller doesn't interact with it

```suggestion
  static targets = ["form", "urlInput", "submitButton", "message", "tableBody"];
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
core/admin.py (1)

9-9: LGTM! Simple admin registration follows existing patterns.

The Inspiration model is registered using the simple registration approach, consistent with other models in this file (Profile, BlogPost, Project, etc.). This provides basic CRUD functionality in the Django admin.

Consider adding a custom admin class for better UX:

@admin.register(Inspiration)
class InspirationAdmin(admin.ModelAdmin):
    list_display = ('project', 'title', 'url', 'date_scraped', 'created_at')
    list_filter = ('project', 'date_scraped', 'created_at')
    search_fields = ('title', 'url', 'description')
    readonly_fields = ('created_at', 'updated_at', 'date_scraped')
    raw_id_fields = ('project',)

This would make it easier to browse and filter inspirations in the admin interface.

Also applies to: 84-84

core/api/schemas.py (1)

267-269: Consider adding URL validation.

The url field in AddInspirationIn could benefit from Pydantic's URL validation to ensure valid URLs are provided.

Apply this diff to add URL validation:

+from pydantic import HttpUrl
+
 class AddInspirationIn(Schema):
     project_id: int
-    url: str
+    url: HttpUrl

Alternatively, if you need to keep it as a string but still validate:

+from pydantic import field_validator
+from urllib.parse import urlparse
+
 class AddInspirationIn(Schema):
     project_id: int
     url: str
+    
+    @field_validator('url')
+    @classmethod
+    def validate_url(cls, v):
+        result = urlparse(v)
+        if not all([result.scheme, result.netloc]):
+            raise ValueError('Invalid URL format')
+        return v
frontend/templates/project/project_inspirations.html (1)

78-159: Consider accessibility improvements for the table.

The table structure is generally good, but consider these accessibility enhancements:

  1. Add aria-label to the delete buttons for screen readers:
 <button
   data-action="inspiration#deleteInspiration"
   data-inspiration-id="{{ inspiration.id }}"
+  aria-label="Delete inspiration: {{ inspiration.title|default:'this inspiration' }}"
   class="text-red-600 hover:text-red-900"
 >
   Delete
 </button>
  1. Consider adding a confirmation dialog before deletion to prevent accidental data loss. The Stimulus controller could handle this with a confirmation prompt.

  2. The "Loading..." default text (line 103) might be confusing if it persists. Consider adding a visual indicator or different fallback text like "Untitled" or "(No title available)".

core/api/views.py (1)

1114-1144: Remove unnecessary f-string prefix.

Line 1134 uses an f-string without placeholders, which is flagged by static analysis.

Apply this diff:

-        return {"status": "success", "message": f"Inspiration deleted successfully"}
+        return {"status": "success", "message": "Inspiration deleted successfully"}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6482908 and 05f190b.

📒 Files selected for processing (12)
  • core/admin.py (2 hunks)
  • core/agent_system_prompts.py (1 hunks)
  • core/agents.py (2 hunks)
  • core/api/schemas.py (1 hunks)
  • core/api/views.py (3 hunks)
  • core/models.py (8 hunks)
  • core/schemas.py (3 hunks)
  • core/urls.py (1 hunks)
  • core/views.py (2 hunks)
  • frontend/src/controllers/inspiration_controller.js (1 hunks)
  • frontend/templates/base_project.html (1 hunks)
  • frontend/templates/project/project_inspirations.html (1 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.html

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.html: Prefer Stimulus JS for adding interactivity to Django templates instead of raw script elements
Leverage Stimulus data attributes to connect HTML elements with JavaScript functionality
Utilize Stimulus targets to reference specific elements within a controller
Employ Stimulus actions to handle user interactions and events

Files:

  • frontend/templates/base_project.html
  • frontend/templates/project/project_inspirations.html
**/*.{html,htm}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always generate semantic HTML

Files:

  • frontend/templates/base_project.html
  • frontend/templates/project/project_inspirations.html
{**/*.{html,htm,css,scss},**/*_controller.@(js|ts)}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Always favor the utility-first Tailwind approach; avoid creating reusable style classes and prefer reuse via template components

Files:

  • frontend/templates/base_project.html
  • frontend/src/controllers/inspiration_controller.js
  • frontend/templates/project/project_inspirations.html
frontend/templates/**/*.html

📄 CodeRabbit inference engine (.cursor/rules/stimulus-events.mdc)

Avoid data-*-outlet links for sibling controller communication when using the event-based approach; keep controllers self-contained

Use semantic HTML elements (e.g., dialog, details/summary) in templates

Files:

  • frontend/templates/base_project.html
  • frontend/templates/project/project_inspirations.html
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)

**/*.py: Prioritize readability and maintainability; follow PEP 8
Use descriptive variable/function names with underscores

In Python, use try-except blocks that catch specific exception types; do not catch broad Exception.

**/*.py: Follow PEP 8 for Python code style
Use descriptive variable names with underscores (snake_case) in Python
Prefer Django's built-in features over external libraries

**/*.py: Use structlog for logging in Python code (avoid print and the standard logging module)
Include contextual key-value fields in log messages (e.g., error=str(e), exc_info=True, profile_id=profile.id)

**/*.py: Use descriptive, full-word variable names; avoid abbreviations and single-letter variables in Python
Provide context in variable names when format or type matters (e.g., include _iso_format, date)
Extract unchanging values into UPPER_CASE constants
Use intermediary variables to name parsed groups instead of using index access directly
Naming conventions: use is_/has_/can_ for booleans; include 'date' for dates; snake_case for variables/functions; PascalCase for classes
Define variables close to where they are used to keep lifespan short
Name things after what they do, not how they're used; ensure names make sense without extra context
Avoid generic names like data, info, manager; use specific, intention-revealing names
Function names should include necessary context without being verbose
If naming is hard, split functions into smaller focused parts
Maintain consistency: reuse the same verbs and nouns for the same concepts; name variables after the functions that create them
Use more descriptive names for longer-lived variables
Avoid else statements by using guard clauses for early returns
Replace simple conditionals with direct assignment when both branches call the same function with different values
Use dictionaries as dispatch tables instead of multiple equal-probability elif chains
Validate input before processing to prevent errors propagating in...

Files:

  • core/agent_system_prompts.py
  • core/api/schemas.py
  • core/urls.py
  • core/admin.py
  • core/api/views.py
  • core/views.py
  • core/agents.py
  • core/schemas.py
  • core/models.py
core/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)

Leverage Django's ORM; avoid raw SQL when possible

Files:

  • core/agent_system_prompts.py
  • core/api/schemas.py
  • core/urls.py
  • core/admin.py
  • core/api/views.py
  • core/views.py
  • core/agents.py
  • core/schemas.py
  • core/models.py
core/api/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/backend.mdc)

core/api/**/*.py: django-ninja generate openapi documentation, so make sure to populate views with relevant data in order for it to show up in the OpenAPI docs.
Use Pydantic models for schema validation
Use django-ninja's authentication classes

Implement REST APIs with django-ninja using Pydantic schemas under core/api/

Files:

  • core/api/schemas.py
  • core/api/views.py
frontend/src/controllers/**/*.js

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

frontend/src/controllers/**/*.js: Use Stimulus controllers to encapsulate JavaScript behavior and keep it separate from HTML structure
New controllers should be created in the frontend/src/controllers directory

Use Stimulus.js controllers for interactive frontend behavior

Files:

  • frontend/src/controllers/inspiration_controller.js
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/stimulus-general.mdc)

**/*.js: Add semicolons at the end of statements in JavaScript files
Use double quotes instead of single quotes for string literals in JavaScript files

Files:

  • frontend/src/controllers/inspiration_controller.js
{**/*.{css,scss},**/*_controller.@(js|ts)}

📄 CodeRabbit inference engine (.cursor/rules/ui-ux-design-guidelines.mdc)

Never create new styles without explicitly receiving permission to do so

Files:

  • frontend/src/controllers/inspiration_controller.js
frontend/src/controllers/**/*_controller.js

📄 CodeRabbit inference engine (.cursor/rules/stimulus-events.mdc)

frontend/src/controllers/**/*_controller.js: Dispatch a CustomEvent from the actor Stimulus controller to signal actions to other controllers
Name custom events descriptively and namespaced (e.g., "suggestion:move")
Set bubbles: true on CustomEvent so ancestor listeners (window/document) can receive it
Include all necessary payload in event.detail (e.g., element and destination)
Dispatch the event from the controller element (this.element.dispatchEvent(event))
In listener controllers, register global event listeners in connect() and remove them in disconnect()
Bind handler methods and keep a reference (e.g., this.boundMove = this.move.bind(this)) to correctly remove listeners
Attach listeners to window or document to catch bubbled custom events from anywhere on the page
Inspect event.detail in the handler and act conditionally based on its contents (e.g., match destination)

Files:

  • frontend/src/controllers/inspiration_controller.js
core/views.py

📄 CodeRabbit inference engine (CLAUDE.md)

Keep Django views skinny: handle request/response only

Files:

  • core/views.py
core/models.py

📄 CodeRabbit inference engine (CLAUDE.md)

core/models.py: Place business logic in Django models (fat models pattern)
Validate simple constraints in the database and place complex domain logic in Django models

Files:

  • core/models.py
🧠 Learnings (1)
📚 Learning: 2025-10-04T08:52:37.437Z
Learnt from: CR
Repo: rasulkireev/TuxSEO PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-04T08:52:37.437Z
Learning: Applies to frontend/src/controllers/**/*.js : Use Stimulus.js controllers for interactive frontend behavior

Applied to files:

  • frontend/src/controllers/inspiration_controller.js
🧬 Code graph analysis (8)
core/agent_system_prompts.py (2)
core/models.py (1)
  • add_inspirations (635-664)
core/schemas.py (1)
  • BlogPostGenerationContext (191-201)
core/api/schemas.py (1)
frontend/src/controllers/inspiration_controller.js (1)
  • url (19-19)
core/urls.py (1)
core/views.py (1)
  • ProjectInspirationsView (866-882)
core/admin.py (1)
core/models.py (1)
  • Inspiration (2037-2105)
core/api/views.py (3)
core/api/schemas.py (4)
  • AddInspirationIn (267-269)
  • AddInspirationOut (272-277)
  • DeleteInspirationIn (280-281)
  • DeleteInspirationOut (284-286)
core/models.py (5)
  • Inspiration (2037-2105)
  • get_page_content (456-484)
  • get_page_content (1408-1432)
  • get_page_content (1515-1539)
  • get_page_content (2079-2105)
frontend/src/controllers/inspiration_controller.js (5)
  • response (41-51)
  • response (87-96)
  • data (53-53)
  • data (98-98)
  • url (19-19)
core/views.py (1)
core/models.py (2)
  • Inspiration (2037-2105)
  • Project (361-871)
core/agents.py (2)
core/agent_system_prompts.py (1)
  • add_inspirations (156-188)
core/models.py (1)
  • add_inspirations (635-664)
core/models.py (4)
core/agent_system_prompts.py (1)
  • add_inspirations (156-188)
core/schemas.py (4)
  • TitleSuggestionContext (138-157)
  • InspirationContext (130-135)
  • BlogPostGenerationContext (191-201)
  • ProjectPageContext (180-188)
core/base_models.py (2)
  • BaseModel (6-12)
  • Meta (11-12)
core/model_utils.py (1)
  • get_markdown_content (102-134)
🪛 Flake8 (7.3.0)
core/api/views.py

[error] 1134-1134: f-string is missing placeholders

(F541)

🪛 Ruff (0.14.2)
core/api/views.py

1095-1101: Consider moving this statement to an else block

(TRY300)


1111-1111: Use explicit conversion flag

Replace with conversion flag

(RUF010)


1134-1134: Consider moving this statement to an else block

(TRY300)


1134-1134: f-string without any placeholders

Remove extraneous f prefix

(F541)


1144-1144: Use explicit conversion flag

Replace with conversion flag

(RUF010)

core/models.py

2073-2073: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (18)
core/urls.py (1)

32-36: LGTM! URL pattern follows existing conventions.

The new inspirations URL pattern is consistent with other project-related patterns in this file and uses the same <int:pk> parameter naming convention.

frontend/templates/base_project.html (1)

118-128: LGTM! Navigation item follows established patterns.

The new Inspirations navigation item is well-integrated with proper active state styling, appropriate icon (lightbulb), and consistent structure matching other sidebar items.

core/agents.py (1)

7-7: LGTM! System prompt integration is clean and consistent.

The add_inspirations function is imported and applied to the content_editor_agent following the same pattern as other system prompt enhancements.

Also applies to: 55-55

core/schemas.py (2)

130-136: LGTM! Schema definition follows Pydantic best practices.

The InspirationContext schema is well-defined with clear field descriptions.


155-157: LGTM! Context integration is clean.

The inspirations field is properly added to both TitleSuggestionContext and BlogPostGenerationContext with appropriate defaults using default_factory=list and clear descriptions.

Also applies to: 199-201

core/api/schemas.py (2)

272-277: LGTM! Response schema is well-structured.

The AddInspirationOut schema provides comprehensive response data including the created inspiration details and optional error messaging.


280-286: LGTM! Delete schemas follow API conventions.

Both DeleteInspirationIn and DeleteInspirationOut follow the same pattern as other delete operations in this file.

frontend/templates/project/project_inspirations.html (4)

10-11: LGTM! Stimulus controller setup is correct.

The controller is properly initialized with the project ID value passed through a data attribute.


42-48: Input field has good accessibility.

The URL input has a proper label with for attribute, placeholder text, and appropriate ARIA attributes through HTML5 input type.


138-157: Empty state is well-designed and user-friendly.

The empty state provides clear guidance with an icon, descriptive text, and a call-to-action button. This follows good UX practices.


34-75: CSRF token handling is properly implemented.

The controller includes the X-CSRFToken header in both POST requests and implements a robust getCsrfToken() method with dual fallback mechanisms—first checking for the CSRF token in a form element, then in cookies. No action needed.

core/views.py (1)

866-883: LGTM! Clean implementation of the inspirations view.

The view correctly filters projects by the user's profile for security, orders inspirations appropriately, and follows Django's class-based view patterns.

core/api/views.py (1)

1066-1112: LGTM! The add_inspiration endpoint is well-implemented.

The endpoint includes proper validation, duplicate checking, security via profile-based project lookup, error handling, and structured logging.

frontend/src/controllers/inspiration_controller.js (1)

1-135: LGTM! Well-structured Stimulus controller.

The controller follows Stimulus.js patterns correctly, handles CSRF tokens properly, provides appropriate user feedback, and adheres to the JavaScript coding guidelines (semicolons, double quotes). The implementation cleanly integrates with the backend API endpoints.

core/models.py (4)

2037-2105: LGTM! Well-designed Inspiration model.

The model follows Django best practices with:

  • Proper field definitions and constraints
  • Unique constraint on (project, url) to prevent duplicates
  • Appropriate Meta configuration
  • Clear __str__ representation
  • get_page_content() method that reuses existing infrastructure via get_markdown_content()

The implementation is consistent with other models in the codebase (e.g., Project, Competitor).


634-675: LGTM! Clean integration of inspirations into title generation.

The system prompt and context building correctly incorporate inspirations to influence title generation. The implementation:

  • Builds InspirationContext from project.inspirations.all()
  • Passes it through the generation deps
  • System prompt provides clear guidance to the AI model

Also applies to: 686-686


921-938: LGTM! Proper integration of inspirations into blog post generation.

The implementation consistently adds inspirations to the blog post generation flow using the same pattern as title generation. The add_inspirations system prompt is imported and registered with the agent.

Also applies to: 961-970, 978-978


1147-1180: LGTM! Consistent integration of inspirations into fix context.

The _build_fix_context method correctly includes inspirations, ensuring that when fixing generated blog posts, the AI has access to the same inspiration sources that were used during initial generation.

Comment on lines +156 to +188
def add_inspirations(ctx: RunContext[BlogPostGenerationContext]) -> str:
if not ctx.deps.inspirations:
return ""

inspiration_sections = []
inspiration_sections.append(
"""
INSPIRATION SOURCES:
The user has shared the following blog posts and blogs as inspiration sources
they would like to emulate in style, tone, and structure.
Use these as reference points when generating content:
"""
)

for inspiration in ctx.deps.inspirations:
inspiration_sections.append(
f"""
- Title: {inspiration.title}
URL: {inspiration.url}
Description: {inspiration.description or 'No description provided'}
"""
)

inspiration_sections.append(
"""
IMPORTANT: Draw inspiration from these sources' writing style, tone, structure,
and approach while creating content that is unique and appropriate for the current project.
Consider how these inspirations structure their content, engage their audience,
and present information.
"""
)

return "\n".join(inspiration_sections)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Code duplication: Similar function exists in core/models.py.

This add_inspirations function is nearly identical to the one in core/models.py (lines 634-663), with only minor differences in the prompt text. Both functions:

  • Accept a RunContext with inspirations
  • Return empty string if no inspirations
  • Build the same markdown structure
  • Use the same format string for each inspiration

Consider extracting a shared utility function to avoid duplication:

def format_inspirations_prompt(inspirations: list[InspirationContext], context_type: str = "content") -> str:
    """Format inspirations into a prompt section.
    
    Args:
        inspirations: List of inspiration contexts
        context_type: Either "content" or "titles" to customize the prompt
    """
    if not inspirations:
        return ""

    sections = [
        """
        INSPIRATION SOURCES:
        The user has shared the following blog posts and blogs as inspiration sources
        they would like to emulate in style, tone, and structure.
        Use these as reference points when generating """ + context_type + """:
        """
    ]

    for inspiration in inspirations:
        sections.append(
            f"""
            - Title: {inspiration.title}
              URL: {inspiration.url}
              Description: {inspiration.description or 'No description provided'}
            """
        )

    sections.append(
        """
        IMPORTANT: Draw inspiration from these sources' writing style, tone, structure,
        and approach while creating """ + context_type + """ that are unique and appropriate for the current project.
        """
    )

    return "\n".join(sections)


def add_inspirations(ctx: RunContext[BlogPostGenerationContext]) -> str:
    return format_inspirations_prompt(ctx.deps.inspirations, "content")

Then update the similar function in core/models.py to use the same utility.

🤖 Prompt for AI Agents
In core/agent_system_prompts.py around lines 156-188, the add_inspirations
function duplicates logic present in core/models.py (lines ~634-663); extract a
single shared helper (e.g., format_inspirations_prompt) that accepts the
inspirations list and a context_type ("content" vs "titles") and returns the
formatted string, replace the local add_inspirations to call that helper, and
update the similar function in core/models.py to call the same helper so both
modules reuse the shared implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants