Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8710989
Add pull request management methods to Git provider fetchers
BrunoV21 Oct 7, 2025
27725c1
Add API endpoints and backend logic for GitHub branch listing, target…
BrunoV21 Oct 28, 2025
f0ac13e
feat(github): add branch comparison method to fetch diff commits betw…
BrunoV21 Oct 28, 2025
259c94b
feat(websockets,prompts): add pull request description generation via…
BrunoV21 Oct 28, 2025
a59d1cd
feat(api): require pr description, add pull request diff endpoint
BrunoV21 Oct 28, 2025
26bf025
build: bump version to 0.1.5 in setup.py
BrunoV21 Oct 28, 2025
bb88e86
Add pull request generation feature with branch selection and automat…
BrunoV21 Oct 29, 2025
0b7b56f
revert prompts
BrunoV21 Oct 29, 2025
7727b5c
feat(ui): add dropdown menu for release notes and PR creation with im…
BrunoV21 Oct 29, 2025
bdf1145
feat(ui): add slide animation and styling for options menu with impro…
BrunoV21 Oct 29, 2025
75f704c
style(ui): standardize button sizing and layout for recap controls
BrunoV21 Oct 29, 2025
1b7c0ad
style(ui): remove unused recap-main-btn CSS class definition
BrunoV21 Oct 29, 2025
ea6c11f
fix(ui): disable release mode button when multiple repos selected or …
BrunoV21 Oct 29, 2025
337578d
feat(ui): add tooltips to disabled menu buttons explaining repository…
BrunoV21 Oct 29, 2025
f0ef35b
refactor(ui): consolidate PR mode sections into unified output layout
BrunoV21 Oct 30, 2025
38f0cc4
style(ui): adjust PR mode layout to horizontal inline design with red…
BrunoV21 Oct 30, 2025
eeb134f
style(ui): adjust PR branch selector layout spacing and alignment
BrunoV21 Oct 30, 2025
8923ef0
style(ui): update PR generate button styling for consistent sizing an…
BrunoV21 Oct 31, 2025
3409f0e
refactor(ui): merge PR generation and creation into single action button
BrunoV21 Oct 31, 2025
014e5ff
feat(ui): replace PR validation message with popup notifications for …
BrunoV21 Oct 31, 2025
ba3aae7
fix(ui): auto-scroll to bottom when streaming PR description updates
BrunoV21 Oct 31, 2025
a46045e
feat(ui): add auto-scroll to summary section when PR generation starts
BrunoV21 Oct 31, 2025
8d3a1d0
feat(api): add branch sorting to list and valid target branch responses
BrunoV21 Oct 31, 2025
ab378f9
feat(pr): add progress indicators for diff fetching and description g…
BrunoV21 Oct 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 99 additions & 5 deletions app/api/models/schemas.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,108 @@
from pydantic import BaseModel, model_validator
from typing import Dict, Self, Optional, Any
from pydantic import BaseModel, model_validator, Field
from typing import Dict, Self, Optional, Any, List
import ulid

class ChatRequest(BaseModel):
session_id: str=""
session_id: str = ""
message: str
model_params: Optional[Dict[str, Any]] = None

@model_validator(mode="after")
def set_session_id(self)->Self:
def set_session_id(self) -> Self:
if not self.session_id:
self.session_id = ulid.ulid()
return self
return self


# --- Branch Listing ---
class BranchListResponse(BaseModel):
branches: List[str] = Field(..., description="List of branch names in the repository.")

@model_validator(mode='after')
def sort_branches(self):
"""Sort branches with main/master at the top, then alphabetically."""
priority_branches = []
other_branches = []

for branch in self.branches:
if branch.lower() in ('main', 'master'):
priority_branches.append(branch)
else:
other_branches.append(branch)

# Sort priority branches (main, master) and other branches separately
priority_branches.sort(key=lambda x: (x.lower() != 'main', x.lower()))
other_branches.sort()

self.branches = priority_branches + other_branches
return self


# --- Valid Target Branches ---
class ValidTargetBranchesRequest(BaseModel):
session_id: str = Field(..., description="Session identifier.")
repo: str = Field(..., description="Repository name.")
source_branch: str = Field(..., description="Source branch name.")

class ValidTargetBranchesResponse(BaseModel):
valid_target_branches: List[str] = Field(..., description="List of valid target branch names.")

@model_validator(mode='after')
def sort_branches(self):
"""Sort branches with main/master at the top, then alphabetically."""
priority_branches = []
other_branches = []

for branch in self.valid_target_branches:
if branch.lower() in ('main', 'master'):
priority_branches.append(branch)
else:
other_branches.append(branch)

# Sort priority branches (main, master) and other branches separately
priority_branches.sort(key=lambda x: (x.lower() != 'main', x.lower()))
other_branches.sort()

self.valid_target_branches = priority_branches + other_branches
return self


# --- Pull Request Creation ---
class CreatePullRequestRequest(BaseModel):
session_id: str = Field(..., description="Session identifier.")
repo: str = Field(..., description="Repository name.")
source_branch: str = Field(..., description="Source branch name.")
target_branch: str = Field(..., description="Target branch name.")
title: Optional[str] = Field(None, description="Title of the pull request.")
description: str = Field(..., description="Description/body of the pull request. This field is required.")
draft: Optional[bool] = Field(False, description="Whether to create the PR as a draft.")
reviewers: Optional[List[str]] = Field(None, description="List of reviewer usernames.")
assignees: Optional[List[str]] = Field(None, description="List of assignee usernames.")
labels: Optional[List[str]] = Field(None, description="List of label names.")

# --- Pull Request Diff ---
class GetPullRequestDiffRequest(BaseModel):
session_id: str = Field(..., description="Session identifier.")
repo: str = Field(..., description="Repository name.")
source_branch: str = Field(..., description="Source branch name.")
target_branch: str = Field(..., description="Target branch name.")

class GetPullRequestDiffResponse(BaseModel):
commits: List[dict] = Field(..., description="List of commit dicts in the diff.")

class CreatePullRequestResponse(BaseModel):
url: str = Field(..., description="URL of the created pull request.")
number: int = Field(..., description="Pull request number.")
state: str = Field(..., description="State of the pull request (e.g., open, closed).")
success: bool = Field(..., description="Whether the pull request was created successfully.")
# Optionally, include the generated description if LLM was used
generated_description: Optional[str] = Field(None, description="LLM-generated PR description, if applicable.")


# --- Utility: Commit List for PR Description Generation ---
class CommitMessagesForPRDescriptionRequest(BaseModel):
commit_messages: List[str] = Field(..., description="List of commit messages to summarize.")
session_id: str = Field(..., description="Session identifier.")

class PRDescriptionResponse(BaseModel):
description: str = Field(..., description="LLM-generated pull request description.")
106 changes: 95 additions & 11 deletions app/api/server/routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
from fastapi import APIRouter, HTTPException, Request, Query
from pydantic import BaseModel

from models.schemas import (
BranchListResponse,
ValidTargetBranchesRequest,
ValidTargetBranchesResponse,
CreatePullRequestRequest,
CreatePullRequestResponse,
)

from models.schemas import GetPullRequestDiffRequest, GetPullRequestDiffResponse
from services.llm_service import set_llm, get_llm, trim_messages
from services.fetcher_service import store_fetcher, get_fetcher
from git_recap.utils import parse_entries_to_txt, parse_releases_to_txt
Expand Down Expand Up @@ -214,7 +223,7 @@ async def get_release_notes(
# Get fetcher for session
try:
fetcher = get_fetcher(session_id)
except HTTPException as e:
except HTTPException:
raise

# Check if fetcher supports fetch_releases
Expand Down Expand Up @@ -273,13 +282,88 @@ async def get_release_notes(

return {"actions": "\n\n".join([actions_txt, releases_txt])}

# @router.post("/chat")
# async def chat(
# chat_request: ChatRequest
# ):
# try:
# llm = await initialize_llm_session(chat_request.session_id)
# response = await llm.acomplete(chat_request.message)
# return {"response": response}
# except Exception as e:
# raise HTTPException(status_code=500, detail=str(e))
# --- Branch and Pull Request Management Endpoints ---
@router.get("/branches", response_model=BranchListResponse)
async def get_branches(
session_id: str,
repo: str
):
"""
Get all branches for a given repository in the current session.
"""
fetcher = get_fetcher(session_id)
try:
fetcher.repo_filter = [repo]
branches = fetcher.get_branches()
except NotImplementedError:
raise HTTPException(status_code=400, detail="Branch listing is not supported for this provider.")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to fetch branches: {str(e)}")
return BranchListResponse(branches=branches)

@router.post("/valid-target-branches", response_model=ValidTargetBranchesResponse)
async def get_valid_target_branches(
req: ValidTargetBranchesRequest
):
"""
Get all valid target branches for a given source branch in a repository.
"""
fetcher = get_fetcher(req.session_id)
try:
fetcher.repo_filter = [req.repo]
valid_targets = fetcher.get_valid_target_branches(req.source_branch)
except NotImplementedError:
raise HTTPException(status_code=400, detail="Target branch validation is not supported for this provider.")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to validate target branches: {str(e)}")
return ValidTargetBranchesResponse(valid_target_branches=valid_targets)

@router.post("/create-pull-request", response_model=CreatePullRequestResponse)
async def create_pull_request(
req: CreatePullRequestRequest
):
fetcher = get_fetcher(req.session_id)
fetcher.repo_filter = [req.repo]
if not req.description or not req.description.strip():
raise HTTPException(status_code=400, detail="Description is required for pull request creation.")
try:
result = fetcher.create_pull_request(
head_branch=req.source_branch,
base_branch=req.target_branch,
title=req.title or f"Merge {req.source_branch} into {req.target_branch}",
body=req.description,
draft=req.draft or False,
reviewers=req.reviewers,
assignees=req.assignees,
labels=req.labels,
)
except NotImplementedError:
raise HTTPException(status_code=400, detail="Pull request creation is not supported for this provider.")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to create pull request: {str(e)}")
return CreatePullRequestResponse(
url=result.get("url"),
number=result.get("number"),
state=result.get("state"),
success=result.get("success", False),
generated_description=None
)

@router.post("/get-pull-request-diff", response_model=GetPullRequestDiffResponse)
async def get_pull_request_diff(req: GetPullRequestDiffRequest):
fetcher = get_fetcher(req.session_id)
fetcher.repo_filter = [req.repo]
provider = type(fetcher).__name__.lower()
if "github" not in provider:
raise HTTPException(status_code=400, detail="Pull request diff is only supported for GitHub provider.")
try:
commits = fetcher.fetch_branch_diff_commits(req.source_branch, req.target_branch)
except NotImplementedError:
raise HTTPException(status_code=400, detail="Branch diff is not supported for this provider.")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to fetch pull request diff: {str(e)}")
return GetPullRequestDiffResponse(commits=commits)
Loading
Loading