Skip to content

Commit a057734

Browse files
feat(ui): add include submodules toggle to web interface
1 parent 4e259a0 commit a057734

File tree

6 files changed

+119
-86
lines changed

6 files changed

+119
-86
lines changed

src/server/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class IngestRequest(BaseModel):
3737
Glob/regex pattern string for file filtering.
3838
token : str | None
3939
GitHub personal access token (PAT) for accessing private repositories.
40+
include_submodules : bool
41+
Whether to include Git submodules in the analysis.
4042
4143
"""
4244

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

4952
@field_validator("input_text")
5053
@classmethod

src/server/query_processor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ async def process_query(
232232
pattern_type: PatternType,
233233
pattern: str,
234234
token: str | None = None,
235+
*,
236+
include_submodules: bool = False,
235237
) -> IngestResponse:
236238
"""Process a query by parsing input, cloning a repository, and generating a summary.
237239
@@ -250,6 +252,8 @@ async def process_query(
250252
Pattern to include or exclude in the query, depending on the pattern type.
251253
token : str | None
252254
GitHub personal access token (PAT) for accessing private repositories.
255+
include_submodules : bool
256+
Whether to include Git submodules in the analysis.
253257
254258
Returns
255259
-------
@@ -272,6 +276,7 @@ async def process_query(
272276
return IngestErrorResponse(error=str(exc))
273277

274278
query.url = cast("str", query.url)
279+
query.include_submodules = include_submodules
275280
query.max_file_size = max_file_size * 1024 # Convert to bytes since we currently use KB in higher levels
276281
query.ignore_patterns, query.include_patterns = process_patterns(
277282
exclude_patterns=pattern if pattern_type == PatternType.EXCLUDE else None,

src/server/routers/ingest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ async def api_ingest(
4646
pattern_type=ingest_request.pattern_type.value,
4747
pattern=ingest_request.pattern,
4848
token=ingest_request.token,
49+
include_submodules=ingest_request.include_submodules,
4950
)
5051
# limit URL to 255 characters
5152
ingest_counter.labels(status=response.status_code, url=ingest_request.input_text[:255]).inc()

src/server/routers_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ async def _perform_ingestion(
2323
pattern_type: str,
2424
pattern: str,
2525
token: str | None,
26+
*,
27+
include_submodules: bool = False,
2628
) -> JSONResponse:
2729
"""Run ``process_query`` and wrap the result in a ``FastAPI`` ``JSONResponse``.
2830
@@ -37,6 +39,7 @@ async def _perform_ingestion(
3739
pattern_type=pattern_type,
3840
pattern=pattern,
3941
token=token,
42+
include_submodules=include_submodules,
4043
)
4144

4245
if isinstance(result, IngestErrorResponse):

src/server/templates/components/git_form.jinja

Lines changed: 96 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -86,94 +86,109 @@
8686
<!-- PAT checkbox with PAT field below -->
8787
<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">
8888
<!-- PAT checkbox -->
89-
<div class="flex items-center space-x-2">
90-
<label for="showAccessSettings"
91-
class="flex gap-2 text-gray-900 cursor-pointer">
92-
<div class="relative w-6 h-6">
93-
<input type="checkbox"
94-
id="showAccessSettings"
95-
onchange="toggleAccessSettings()"
96-
{% if token %}checked{% endif %}
97-
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]" />
98-
<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"
99-
style="clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%)"></span>
89+
<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">
90+
<div class="flex items-center space-x-2 mb-4">
91+
<label for="include_submodules"
92+
class="flex gap-2 text-gray-900 cursor-pointer">
93+
<div class="relative w-6 h-6">
94+
<input type="checkbox"
95+
id="include_submodules"
96+
name="include_submodules"
97+
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]" />
98+
<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"
99+
style="clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%)"></span>
100+
</div>
101+
Include Submodules
102+
</label>
103+
</div>
104+
<div class="flex items-center space-x-2">
105+
<label for="showAccessSettings"
106+
class="flex gap-2 text-gray-900 cursor-pointer">
107+
<div class="relative w-6 h-6">
108+
<input type="checkbox"
109+
id="showAccessSettings"
110+
onchange="toggleAccessSettings()"
111+
{% if token %}checked{% endif %}
112+
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]" />
113+
<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"
114+
style="clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%)"></span>
115+
</div>
116+
Private Repository
117+
</label>
118+
<span class="badge-new">NEW</span>
119+
</div>
120+
<!-- PAT field -->
121+
<div id="accessSettingsContainer"
122+
class="{% if not token %}hidden {% endif %}mt-3 w-full">
123+
<div class="relative w-full">
124+
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0 z-10"></div>
125+
<div class="flex relative z-20 border-[3px] border-gray-900 rounded bg-white">
126+
<input id="token"
127+
type="password"
128+
name="token"
129+
placeholder="Personal Access Token"
130+
value="{{ token if token else '' }}"
131+
class="py-2 pl-2 pr-8 bg-[#E8F0FE] focus:outline-none w-full rounded">
132+
<!-- Info icon with tooltip -->
133+
<span class="absolute right-3 top-1/2 -translate-y-1/2">
134+
<!-- Icon -->
135+
<svg class="w-4 h-4 text-gray-600 cursor-pointer peer"
136+
xmlns="http://www.w3.org/2000/svg"
137+
fill="none"
138+
viewBox="0 0 24 24"
139+
stroke="currentColor"
140+
stroke-width="2">
141+
<circle cx="12" cy="12" r="10" />
142+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01" />
143+
</svg>
144+
<!-- Tooltip (tooltip listens to peer-hover) -->
145+
<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">
146+
<ul class="list-disc pl-4">
147+
<li>PAT is never stored in the backend</li>
148+
<li>Used once for cloning, then discarded from memory</li>
149+
<li>No browser caching</li>
150+
<li>Cloned repos are deleted after processing</li>
151+
</ul>
152+
</div>
153+
</span>
154+
</div>
100155
</div>
101-
Private Repository
102-
</label>
103-
<span class="badge-new">NEW</span>
104-
</div>
105-
<!-- PAT field -->
106-
<div id="accessSettingsContainer"
107-
class="{% if not token %}hidden {% endif %}mt-3 w-full">
108-
<div class="relative w-full">
109-
<div class="w-full h-full rounded bg-gray-900 translate-y-1 translate-x-1 absolute inset-0 z-10"></div>
110-
<div class="flex relative z-20 border-[3px] border-gray-900 rounded bg-white">
111-
<input id="token"
112-
type="password"
113-
name="token"
114-
placeholder="Personal Access Token"
115-
value="{{ token if token else '' }}"
116-
class="py-2 pl-2 pr-8 bg-[#E8F0FE] focus:outline-none w-full rounded">
117-
<!-- Info icon with tooltip -->
118-
<span class="absolute right-3 top-1/2 -translate-y-1/2">
119-
<!-- Icon -->
120-
<svg class="w-4 h-4 text-gray-600 cursor-pointer peer"
121-
xmlns="http://www.w3.org/2000/svg"
156+
<!-- Help section -->
157+
<div class="mt-2 flex items-center space-x-1">
158+
<a href="https://github.com/settings/tokens/new?description=gitingest&scopes=repo"
159+
target="_blank"
160+
rel="noopener noreferrer"
161+
class="text-sm text-gray-600 hover:text-gray-800 flex items-center space-x-1 underline">
162+
<span>Get your token</span>
163+
<svg class="w-3 h-3"
122164
fill="none"
123-
viewBox="0 0 24 24"
124165
stroke="currentColor"
125-
stroke-width="2">
126-
<circle cx="12" cy="12" r="10" />
127-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01" />
166+
viewBox="0 0 24 24"
167+
xmlns="http://www.w3.org/2000/svg">
168+
<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" />
128169
</svg>
129-
<!-- Tooltip (tooltip listens to peer-hover) -->
130-
<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">
131-
<ul class="list-disc pl-4">
132-
<li>PAT is never stored in the backend</li>
133-
<li>Used once for cloning, then discarded from memory</li>
134-
<li>No browser caching</li>
135-
<li>Cloned repos are deleted after processing</li>
136-
</ul>
137-
</div>
138-
</span>
170+
</a>
139171
</div>
140172
</div>
141-
<!-- Help section -->
142-
<div class="mt-2 flex items-center space-x-1">
143-
<a href="https://github.com/settings/tokens/new?description=gitingest&scopes=repo"
144-
target="_blank"
145-
rel="noopener noreferrer"
146-
class="text-sm text-gray-600 hover:text-gray-800 flex items-center space-x-1 underline">
147-
<span>Get your token</span>
148-
<svg class="w-3 h-3"
149-
fill="none"
150-
stroke="currentColor"
151-
viewBox="0 0 24 24"
152-
xmlns="http://www.w3.org/2000/svg">
153-
<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" />
154-
</svg>
155-
</a>
156-
</div>
157173
</div>
158174
</div>
159-
</div>
160-
</form>
161-
<!-- Example repositories section -->
162-
{% if show_examples %}
163-
<div id="exampleRepositories"
164-
class="{% if token %}lg:mt-0 {% endif %} mt-4">
165-
<p class="opacity-70 mb-1">Try these example repositories:</p>
166-
<div class="flex flex-wrap gap-2">
167-
{% for example in examples %}
168-
<button onclick="submitExample('{{ example.url }}')"
169-
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">
170-
{{ example.name }}
171-
</button>
172-
{% endfor %}
175+
</form>
176+
<!-- Example repositories section -->
177+
{% if show_examples %}
178+
<div id="exampleRepositories"
179+
class="{% if token %}lg:mt-0 {% endif %} mt-4">
180+
<p class="opacity-70 mb-1">Try these example repositories:</p>
181+
<div class="flex flex-wrap gap-2">
182+
{% for example in examples %}
183+
<button onclick="submitExample('{{ example.url }}')"
184+
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">
185+
{{ example.name }}
186+
</button>
187+
{% endfor %}
188+
</div>
173189
</div>
174-
</div>
175-
{% endif %}
190+
{% endif %}
191+
</div>
176192
</div>
177-
</div>
178-
<script defer src="/static/js/git.js"></script>
179-
<script defer src="/static/js/git_form.js"></script>
193+
<script defer src="/static/js/git.js"></script>
194+
<script defer src="/static/js/git_form.js"></script>

src/static/js/utils.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,17 @@ function collectFormData(form) {
130130
const patternType = document.getElementById('pattern_type');
131131
const pattern = document.getElementById('pattern');
132132

133-
if (inputText) {json_data.input_text = inputText.value;}
134-
if (token) {json_data.token = token.value;}
135-
if (hiddenInput) {json_data.max_file_size = hiddenInput.value;}
136-
if (patternType) {json_data.pattern_type = patternType.value;}
137-
if (pattern) {json_data.pattern = pattern.value;}
133+
// Add this line to capture the submodule toggle
134+
const includeSubmodules = document.getElementById('include_submodules');
135+
136+
if (inputText) { json_data.input_text = inputText.value; }
137+
if (token) { json_data.token = token.value; }
138+
if (hiddenInput) { json_data.max_file_size = hiddenInput.value; }
139+
if (patternType) { json_data.pattern_type = patternType.value; }
140+
if (pattern) { json_data.pattern = pattern.value; }
141+
142+
// Set the boolean value for the backend
143+
if (includeSubmodules) { json_data.include_submodules = includeSubmodules.checked; }
138144

139145
return json_data;
140146
}

0 commit comments

Comments
 (0)