Created
November 8, 2025 13:55
-
-
Save rtviii/2fba763083f2a51d6dce5a23c5fe8d31 to your computer and use it in GitHub Desktop.
Tired of curating the context by by grabbing individual files, just select to add to prompt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import sys | |
| import socket | |
| import argparse | |
| from pathlib import Path | |
| from nicegui import ui | |
| from fastapi import FastAPI | |
| import uvicorn | |
| PROJECT_DIR = None | |
| selected_files = {} | |
| prompt_display = None | |
| def get_directory_structure(path, parent_path=""): | |
| items = {'dirs': [], 'files': []} | |
| try: | |
| for item in sorted(os.listdir(path)): | |
| if item.startswith('.'): | |
| continue | |
| item_path = os.path.join(path, item) | |
| rel_path = os.path.join(parent_path, item) if parent_path else item | |
| if os.path.isdir(item_path): | |
| items['dirs'].append({'name': item, 'path': rel_path, 'full_path': item_path}) | |
| else: | |
| items['files'].append({'name': item, 'path': rel_path, 'full_path': item_path}) | |
| except PermissionError: | |
| pass | |
| return items | |
| def read_file(filepath): | |
| try: | |
| with open(filepath, 'r', encoding='utf-8') as f: | |
| return f.read() | |
| except Exception as e: | |
| return f"Error reading file: {e}" | |
| def update_prompt(): | |
| prompt = "" | |
| for filepath in selected_files.keys(): | |
| content = read_file(os.path.join(PROJECT_DIR, filepath)) | |
| ext = filepath.split('.')[-1] if '.' in filepath else '' | |
| prompt += f"{filepath}\n```{ext}\n{content}\n```\n\n" | |
| prompt_display.set_content(prompt) | |
| def reset_all(): | |
| selected_files.clear() | |
| prompt_display.set_content('') | |
| ui.notify('Reset all selections', type='info') | |
| def render_directory_contents(container, path, parent_path=""): | |
| items = get_directory_structure(path, parent_path) | |
| for dir_item in items['dirs']: | |
| with container: | |
| expansion = ui.expansion(dir_item['name']).classes('w-full').props('dense header-class="text-weight-bold"').style('margin: 0; padding: 0; font-size: 13px') | |
| child_container = ui.column().style('margin-left: 12px; padding: 0; gap: 0; margin-top: 0') | |
| # Lazy load: render children when expanded, clear when collapsed | |
| def toggle_handler(e, path=dir_item['full_path'], ppath=dir_item['path'], cont=child_container): | |
| if e.args: # Expanded | |
| render_directory_contents(cont, path, ppath) | |
| else: # Collapsed | |
| cont.clear() | |
| expansion.on('update:model-value', toggle_handler) | |
| with expansion: | |
| child_container | |
| # Then files | |
| for file_item in items['files']: | |
| with container: | |
| def make_handler(filepath, full_path): | |
| def handler(e): | |
| if e.value: # Checked | |
| selected_files[filepath] = True | |
| else: # Unchecked | |
| selected_files.pop(filepath, None) | |
| update_prompt() | |
| return handler | |
| checkbox = ui.checkbox(file_item['name']).classes('w-full').props('dense size=xs').style('margin: 0; padding: 0; font-size: 13px') | |
| checkbox.on_value_change(make_handler(file_item['path'], file_item['full_path'])) | |
| async def copy_to_clipboard(): | |
| prompt = "" | |
| for filepath in selected_files.keys(): | |
| content = read_file(os.path.join(PROJECT_DIR, filepath)) | |
| ext = filepath.split('.')[-1] if '.' in filepath else '' | |
| prompt += f"{filepath}\n```{ext}\n{content}\n```\n\n" | |
| ui.run_javascript(f''' | |
| navigator.clipboard.writeText({repr(prompt)}); | |
| ''') | |
| ui.notify('Copied to clipboard!', type='positive') | |
| def create_ui(): | |
| @ui.page('/') | |
| def main_page(): | |
| global prompt_display | |
| ui.add_head_html(''' | |
| <style> | |
| .q-expansion-item__container { | |
| padding: 0 !important; | |
| margin: 0 !important; | |
| } | |
| .q-expansion-item__content { | |
| padding: 0 !important; | |
| margin: 0 !important; | |
| min-height: 0 !important; | |
| display: none !important; | |
| } | |
| .q-expansion-item__content > * { | |
| display: block !important; | |
| } | |
| .q-item { | |
| min-height: 18px !important; | |
| padding: 2px 8px !important; | |
| margin: 0 !important; | |
| } | |
| .q-checkbox { | |
| margin: 0 !important; | |
| padding: 0 !important; | |
| } | |
| .q-expansion-item { | |
| margin: 0 !important; | |
| padding: 0 !important; | |
| } | |
| /* Hide scrollbar but keep functionality */ | |
| ::-webkit-scrollbar { | |
| display: none; | |
| } | |
| * { | |
| -ms-overflow-style: none; | |
| scrollbar-width: none; | |
| } | |
| </style> | |
| ''') | |
| with ui.row().classes('w-full gap-0').style('height: 100vh; margin: 0; padding: 0'): | |
| with ui.column().classes('border-r overflow-y-auto bg-gray-50').style('width: 500px; height: 100vh; overflow-x: hidden; padding: 12px; gap: 0'): | |
| tree_container = ui.column().classes('w-full').style('padding: 0; margin: 0; gap: 0') | |
| render_directory_contents(tree_container, PROJECT_DIR) | |
| with ui.column().classes('flex-1 p-4').style('height: 100vh; overflow: hidden'): | |
| with ui.row().classes('w-full items-center mb-2').style('gap: 8px'): | |
| ui.button('Copy', on_click=copy_to_clipboard, icon='content_copy').props('flat dense') | |
| ui.button('Reset', on_click=reset_all, icon='refresh').props('flat dense') | |
| ui.space() | |
| with ui.scroll_area().classes('w-full').style('height: calc(100vh - 80px)'): | |
| prompt_display = ui.markdown().classes('w-full') | |
| def setup_app(): | |
| """Configures and returns the FastAPI app.""" | |
| app = FastAPI() | |
| create_ui() | |
| ui.run_with(app, title="Prompt Builder") | |
| return app | |
| def get_local_ip(): | |
| """Get the local IP address""" | |
| try: | |
| s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
| s.connect(("8.8.8.8", 80)) | |
| ip = s.getsockname()[0] | |
| s.close() | |
| return ip | |
| except Exception: | |
| return "localhost" | |
| if __name__ in {"__main__", "__mp_main__"}: | |
| parser = argparse.ArgumentParser(description='Prompt Builder') | |
| parser.add_argument('project_dir', type=str, help='Project directory to browse') | |
| parser.add_argument('--port', type=int, default=8082, help='Port to run server on') | |
| parser.add_argument('--host', type=str, default='0.0.0.0', help='Host to bind to') | |
| args = parser.parse_args() | |
| PROJECT_DIR = os.path.abspath(args.project_dir) | |
| if not os.path.isdir(PROJECT_DIR): | |
| print(f"Error: {PROJECT_DIR} is not a valid directory") | |
| sys.exit(1) | |
| app = setup_app() | |
| local_ip = get_local_ip() | |
| hostname = socket.gethostname() | |
| print(f" Local: http://localhost:{args.port}") | |
| print(f" ssh -L {args.port}:{hostname}:{args.port} [YOUR_USERNAME]@{hostname}") | |
| uvicorn.run( | |
| app, | |
| host=args.host, | |
| port=args.port, | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment