Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class IngestRequest(BaseModel):
Glob/regex pattern string for file filtering.
token : str | None
GitHub personal access token (PAT) for accessing private repositories.
include_submodules : bool
Whether to include Git submodules in the analysis.

"""

Expand All @@ -45,6 +47,7 @@ class IngestRequest(BaseModel):
pattern_type: PatternType = Field(default=PatternType.EXCLUDE, description="Pattern type for file filtering")
pattern: str = Field(default="", description="Glob/regex pattern for file filtering")
token: str | None = Field(default=None, description="GitHub PAT for private repositories")
include_submodules: bool = Field(default=False, description="Whether to include Git submodules")

@field_validator("input_text")
@classmethod
Expand Down
5 changes: 5 additions & 0 deletions src/server/query_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ async def process_query(
pattern_type: PatternType,
pattern: str,
token: str | None = None,
*,
include_submodules: bool = False,
) -> IngestResponse:
"""Process a query by parsing input, cloning a repository, and generating a summary.

Expand All @@ -250,6 +252,8 @@ async def process_query(
Pattern to include or exclude in the query, depending on the pattern type.
token : str | None
GitHub personal access token (PAT) for accessing private repositories.
include_submodules : bool
Whether to include Git submodules in the analysis.

Returns
-------
Expand All @@ -272,6 +276,7 @@ async def process_query(
return IngestErrorResponse(error=str(exc))

query.url = cast("str", query.url)
query.include_submodules = include_submodules
query.max_file_size = max_file_size * 1024 # Convert to bytes since we currently use KB in higher levels
query.ignore_patterns, query.include_patterns = process_patterns(
exclude_patterns=pattern if pattern_type == PatternType.EXCLUDE else None,
Expand Down
1 change: 1 addition & 0 deletions src/server/routers/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ async def api_ingest(
pattern_type=ingest_request.pattern_type.value,
pattern=ingest_request.pattern,
token=ingest_request.token,
include_submodules=ingest_request.include_submodules,
)
# limit URL to 255 characters
ingest_counter.labels(status=response.status_code, url=ingest_request.input_text[:255]).inc()
Expand Down
3 changes: 3 additions & 0 deletions src/server/routers_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ async def _perform_ingestion(
pattern_type: str,
pattern: str,
token: str | None,
*,
include_submodules: bool = False,
) -> JSONResponse:
"""Run ``process_query`` and wrap the result in a ``FastAPI`` ``JSONResponse``.

Expand All @@ -37,6 +39,7 @@ async def _perform_ingestion(
pattern_type=pattern_type,
pattern=pattern,
token=token,
include_submodules=include_submodules,
)

if isinstance(result, IngestErrorResponse):
Expand Down
177 changes: 96 additions & 81 deletions src/server/templates/components/git_form.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -86,94 +86,109 @@
<!-- PAT checkbox with PAT field below -->
<div class="flex flex-col items-start w-full sm:col-span-2 lg:col-span-1 lg:row-span-2 lg:pt-3.5">
<!-- PAT checkbox -->
<div class="flex items-center space-x-2">
<label for="showAccessSettings"
class="flex gap-2 text-gray-900 cursor-pointer">
<div class="relative w-6 h-6">
<input type="checkbox"
id="showAccessSettings"
onchange="toggleAccessSettings()"
{% if token %}checked{% endif %}
class="cursor-pointer peer appearance-none w-full h-full rounded-sm border-[3px] border-current bg-white m-0 text-current shadow-[3px_3px_0_currentColor]" />
<span class="absolute inset-0 w-3 h-3 m-auto scale-0 transition-transform duration-150 ease-in-out shadow-[inset_1rem_1rem_#FE4A60] bg-[CanvasText] origin-bottom-left peer-checked:scale-100"
style="clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%)"></span>
<div class="flex flex-col items-start w-full sm:col-span-2 lg:col-span-1 lg:row-span-2 lg:pt-3.5">
<div class="flex items-center space-x-2 mb-4">
<label for="include_submodules"
class="flex gap-2 text-gray-900 cursor-pointer">
<div class="relative w-6 h-6">
<input type="checkbox"
id="include_submodules"
name="include_submodules"
class="cursor-pointer peer appearance-none w-full h-full rounded-sm border-[3px] border-current bg-white m-0 text-current shadow-[3px_3px_0_currentColor]" />
<span class="absolute inset-0 w-3 h-3 m-auto scale-0 transition-transform duration-150 ease-in-out shadow-[inset_1rem_1rem_#FE4A60] bg-[CanvasText] origin-bottom-left peer-checked:scale-100"
style="clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%)"></span>
</div>
Include Submodules
</label>
</div>
<div class="flex items-center space-x-2">
<label for="showAccessSettings"
class="flex gap-2 text-gray-900 cursor-pointer">
<div class="relative w-6 h-6">
<input type="checkbox"
id="showAccessSettings"
onchange="toggleAccessSettings()"
{% if token %}checked{% endif %}
class="cursor-pointer peer appearance-none w-full h-full rounded-sm border-[3px] border-current bg-white m-0 text-current shadow-[3px_3px_0_currentColor]" />
<span class="absolute inset-0 w-3 h-3 m-auto scale-0 transition-transform duration-150 ease-in-out shadow-[inset_1rem_1rem_#FE4A60] bg-[CanvasText] origin-bottom-left peer-checked:scale-100"
style="clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%)"></span>
</div>
Private Repository
</label>
<span class="badge-new">NEW</span>
</div>
<!-- PAT field -->
<div id="accessSettingsContainer"
class="{% if not token %}hidden {% endif %}mt-3 w-full">
<div class="relative w-full">
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0 z-10"></div>
<div class="flex relative z-20 border-[3px] border-gray-900 rounded bg-white">
<input id="token"
type="password"
name="token"
placeholder="Personal Access Token"
value="{{ token if token else '' }}"
class="py-2 pl-2 pr-8 bg-[#E8F0FE] focus:outline-none w-full rounded">
<!-- Info icon with tooltip -->
<span class="absolute right-3 top-1/2 -translate-y-1/2">
<!-- Icon -->
<svg class="w-4 h-4 text-gray-600 cursor-pointer peer"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2">
<circle cx="12" cy="12" r="10" />
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01" />
</svg>
<!-- Tooltip (tooltip listens to peer-hover) -->
<div class="absolute bottom-full mb-2 left-1/2 -translate-x-1/2 bg-gray-900 text-white text-xs leading-tight py-1 px-2 rounded shadow-lg opacity-0 pointer-events-none peer-hover:opacity-100 peer-hover:pointer-events-auto transition-opacity duration-200 whitespace-nowrap">
<ul class="list-disc pl-4">
<li>PAT is never stored in the backend</li>
<li>Used once for cloning, then discarded from memory</li>
<li>No browser caching</li>
<li>Cloned repos are deleted after processing</li>
</ul>
</div>
</span>
</div>
</div>
Private Repository
</label>
<span class="badge-new">NEW</span>
</div>
<!-- PAT field -->
<div id="accessSettingsContainer"
class="{% if not token %}hidden {% endif %}mt-3 w-full">
<div class="relative w-full">
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0 z-10"></div>
<div class="flex relative z-20 border-[3px] border-gray-900 rounded bg-white">
<input id="token"
type="password"
name="token"
placeholder="Personal Access Token"
value="{{ token if token else '' }}"
class="py-2 pl-2 pr-8 bg-[#E8F0FE] focus:outline-none w-full rounded">
<!-- Info icon with tooltip -->
<span class="absolute right-3 top-1/2 -translate-y-1/2">
<!-- Icon -->
<svg class="w-4 h-4 text-gray-600 cursor-pointer peer"
xmlns="http://www.w3.org/2000/svg"
<!-- Help section -->
<div class="mt-2 flex items-center space-x-1">
<a href="https://github.com/settings/tokens/new?description=gitingest&scopes=repo"
target="_blank"
rel="noopener noreferrer"
class="text-sm text-gray-600 hover:text-gray-800 flex items-center space-x-1 underline">
<span>Get your token</span>
<svg class="w-3 h-3"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2">
<circle cx="12" cy="12" r="10" />
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01" />
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
<!-- Tooltip (tooltip listens to peer-hover) -->
<div class="absolute bottom-full mb-2 left-1/2 -translate-x-1/2 bg-gray-900 text-white text-xs leading-tight py-1 px-2 rounded shadow-lg opacity-0 pointer-events-none peer-hover:opacity-100 peer-hover:pointer-events-auto transition-opacity duration-200 whitespace-nowrap">
<ul class="list-disc pl-4">
<li>PAT is never stored in the backend</li>
<li>Used once for cloning, then discarded from memory</li>
<li>No browser caching</li>
<li>Cloned repos are deleted after processing</li>
</ul>
</div>
</span>
</a>
</div>
</div>
<!-- Help section -->
<div class="mt-2 flex items-center space-x-1">
<a href="https://github.com/settings/tokens/new?description=gitingest&scopes=repo"
target="_blank"
rel="noopener noreferrer"
class="text-sm text-gray-600 hover:text-gray-800 flex items-center space-x-1 underline">
<span>Get your token</span>
<svg class="w-3 h-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</div>
</div>
</div>
</div>
</form>
<!-- Example repositories section -->
{% if show_examples %}
<div id="exampleRepositories"
class="{% if token %}lg:mt-0 {% endif %} mt-4">
<p class="opacity-70 mb-1">Try these example repositories:</p>
<div class="flex flex-wrap gap-2">
{% for example in examples %}
<button onclick="submitExample('{{ example.url }}')"
class="px-4 py-1 bg-[#EBDBB7] hover:bg-[#FFC480] text-gray-900 rounded transition-colors duration-200 border-[3px] border-gray-900 relative hover:-translate-y-px hover:-translate-x-px">
{{ example.name }}
</button>
{% endfor %}
</form>
<!-- Example repositories section -->
{% if show_examples %}
<div id="exampleRepositories"
class="{% if token %}lg:mt-0 {% endif %} mt-4">
<p class="opacity-70 mb-1">Try these example repositories:</p>
<div class="flex flex-wrap gap-2">
{% for example in examples %}
<button onclick="submitExample('{{ example.url }}')"
class="px-4 py-1 bg-[#EBDBB7] hover:bg-[#FFC480] text-gray-900 rounded transition-colors duration-200 border-[3px] border-gray-900 relative hover:-translate-y-px hover:-translate-x-px">
{{ example.name }}
</button>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
<script defer src="/static/js/git.js"></script>
<script defer src="/static/js/git_form.js"></script>
<script defer src="/static/js/git.js"></script>
<script defer src="/static/js/git_form.js"></script>
16 changes: 11 additions & 5 deletions src/static/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,17 @@ function collectFormData(form) {
const patternType = document.getElementById('pattern_type');
const pattern = document.getElementById('pattern');

if (inputText) {json_data.input_text = inputText.value;}
if (token) {json_data.token = token.value;}
if (hiddenInput) {json_data.max_file_size = hiddenInput.value;}
if (patternType) {json_data.pattern_type = patternType.value;}
if (pattern) {json_data.pattern = pattern.value;}
// Add this line to capture the submodule toggle
const includeSubmodules = document.getElementById('include_submodules');

if (inputText) { json_data.input_text = inputText.value; }
if (token) { json_data.token = token.value; }
if (hiddenInput) { json_data.max_file_size = hiddenInput.value; }
if (patternType) { json_data.pattern_type = patternType.value; }
if (pattern) { json_data.pattern = pattern.value; }

// Set the boolean value for the backend
if (includeSubmodules) { json_data.include_submodules = includeSubmodules.checked; }

return json_data;
}
Expand Down