Files
RePrompt/src/app.py
Keep Creating Online dc5d918440 update
2025-01-16 16:18:51 -05:00

272 lines
9.4 KiB
Python

import streamlit as st
from pathlib import Path
import os
import yaml
from tkinter import Tk
from tkinter.filedialog import askdirectory
import subprocess
import sys
import pyperclip
# Initialize session state variables
if 'copied' not in st.session_state:
st.session_state.copied = False
# Configuration
CONFIG_FILE = "config.yaml"
SCRIPT_DIR = Path(__file__).parent
# Default exclusions
DEFAULT_EXCLUDED_DIRS = ["node_modules", "venv", "__pycache__", ".git", "logs", ".idea", ".vscode"]
DEFAULT_EXCLUDED_FILES = [
"repo-context.txt",
"package-lock.json",
"yarn.lock",
".gitattributes",
".gitignore",
".dockerignore",
"*.pem",
"*.crt",
"*.key",
".eslintrc",
".prettierrc",
".browserslistrc",
".editorconfig",
"client.crt",
"client-key.pem"
]
# Load saved configuration from repository directory
def load_saved_config(repo_path):
saved_config_path = repo_path / "saved_config.yaml"
try:
if saved_config_path.exists():
with open(saved_config_path, "r") as f:
saved_config = yaml.safe_load(f)
return saved_config if saved_config else {}
return {}
except Exception:
return {}
# Save configuration to repository directory
def save_config(config, repo_path):
try:
if not config: # Don't save empty configs
return
saved_config_path = repo_path / "saved_config.yaml"
with open(saved_config_path, "w") as f:
yaml.dump(config, f, default_flow_style=False)
except Exception:
pass
# Load application configuration
def load_config():
config_path = SCRIPT_DIR / CONFIG_FILE
if not config_path.exists():
st.error(f"Configuration file {CONFIG_FILE} not found.")
st.stop()
try:
with open(config_path, "r") as f:
return yaml.safe_load(f)
except yaml.YAMLError as e:
st.error(f"Error parsing configuration file: {e}")
st.stop()
# Clear config.yaml
def clear_config():
config_path = SCRIPT_DIR / CONFIG_FILE
with open(config_path, "w") as f:
yaml.dump({}, f)
app_config = load_config()
exclude_dirs = app_config.get("exclude_dirs", DEFAULT_EXCLUDED_DIRS)
# Initialize session state for selected_repo_path if not present
if "selected_repo_path" not in st.session_state:
st.session_state["selected_repo_path"] = None
# Streamlit App
st.title("RePrompt: A Context Generator")
# Folder Selection
st.sidebar.header("Select a Folder")
def select_folder():
try:
root = Tk()
root.withdraw() # Hide the main window
root.wm_attributes('-topmost', True) # Keep on top
folder_path = askdirectory(parent=root) # Specify parent window
if folder_path:
st.session_state["selected_repo_path"] = folder_path
return True
return False
except Exception as e:
st.error(f"Error selecting folder: {str(e)}")
return False
finally:
try:
root.destroy() # Ensure window is destroyed
except:
pass # Ignore any errors during cleanup
if st.sidebar.button("Choose Folder"):
if select_folder():
st.sidebar.success(f"Selected folder: {st.session_state['selected_repo_path']}")
else:
st.sidebar.error("No folder selected.")
# Load previously selected folder
selected_repo_path = st.session_state.get("selected_repo_path", None)
if selected_repo_path:
st.header(f"Selected Repository: {selected_repo_path}")
repo_path = Path(selected_repo_path)
current_config = load_saved_config(repo_path) # Load config from repo directory
st.subheader("File Filtering")
# Retrieve directories and files in the repository
all_directories = []
all_files = []
for root_dir, dirs, files in os.walk(repo_path):
rel_root = Path(root_dir).relative_to(repo_path)
# Exclude default directories
dirs[:] = [d for d in dirs if d not in DEFAULT_EXCLUDED_DIRS]
# Add directories
for d in dirs:
all_directories.append(str(rel_root / d) + "/")
# Add files (including root files)
for f in files:
# Skip files that match any of the default excluded patterns
if any(f.endswith(excluded.replace('*', '')) for excluded in DEFAULT_EXCLUDED_FILES if '*' in excluded) or \
f in DEFAULT_EXCLUDED_FILES:
continue
file_path = str(rel_root / f)
if file_path.startswith('.'): # Handle root files
file_path = file_path[2:] # Remove './'
all_files.append(file_path)
# Directory selection for Directory Tree
# Filter out any saved directories that don't exist in current options
saved_directories = current_config.get("selected_directories", [])
valid_saved_directories = [d for d in saved_directories if d in all_directories]
selected_directories = st.multiselect(
"Include in Directory Tree",
options=all_directories,
default=valid_saved_directories
)
# Include all files in selected directories AND root files
included_files = [
f for f in all_files if (
any(str(Path(f).parent) in d for d in selected_directories) or # Files in selected directories
str(Path(f).parent) == '.' or # Root files
str(Path(f).parent) == '' # Also handles root files
)
]
# File exclusions
available_files = [f for f in included_files if f not in DEFAULT_EXCLUDED_FILES]
saved_exclusions = [f for f in current_config.get("excluded_files", [])
if f in available_files and f not in DEFAULT_EXCLUDED_FILES]
excluded_files = st.multiselect(
"Exclude Specific Files",
options=available_files,
default=saved_exclusions
)
st.write("### Final Included Files")
final_included_files = [f for f in included_files if f not in excluded_files]
st.write(final_included_files)
st.subheader("Generate Context File")
if st.button("Generate Context File"):
try:
# Update config.yaml based on user selections
updated_config = {
"source_directory": str(repo_path),
"exclude_dirs": DEFAULT_EXCLUDED_DIRS,
"important_files": final_included_files,
"custom_sections": app_config.get("custom_sections", [])
}
# Write updated config.yaml
with open(SCRIPT_DIR / CONFIG_FILE, "w") as f:
yaml.dump(updated_config, f)
# Run the script as a subprocess
result = subprocess.run(
[sys.executable, str(SCRIPT_DIR / "generate_repo_context.py")],
cwd=SCRIPT_DIR,
check=True,
capture_output=True,
text=True,
)
# Clear config.yaml after generation
clear_config()
st.success("Context file generated successfully.")
st.write(f"Script output:\n{result.stdout}")
# Check if the file was created in script directory first
generated_file = SCRIPT_DIR / "repo-context.txt"
if generated_file.exists():
# Read content
with open(generated_file, "r", encoding="utf-8") as f:
context_content = f.read()
# Copy file to repository directory
repo_context_file = repo_path / "repo-context.txt"
with open(repo_context_file, "w", encoding="utf-8") as f:
f.write(context_content)
# Delete the file from script directory
generated_file.unlink()
if context_content.strip(): # Ensure content is not empty
# Add Download Button with unique key
st.download_button(
label="Download repo-context.txt",
data=context_content,
file_name="repo-context.txt",
mime="text/plain",
key="download_button_1"
)
st.info("To copy: Click in the text area, press Ctrl+A (Cmd+A on Mac) to select all, then Ctrl+C (Cmd+C on Mac) to copy.")
# Create a text area with the content
text_area = st.text_area(
"Generated Context File",
value=context_content,
height=400,
key="context_content"
)
st.success(f"Context file saved to: {repo_context_file}")
else:
st.error("Generated content is empty. Please review your repository and configurations.")
else:
st.error("Context file not found after script execution.")
except subprocess.CalledProcessError as e:
st.error("Error generating context file:")
if e.stdout:
st.text(f"Standard Output:\n{e.stdout}")
if e.stderr:
st.text(f"Standard Error:\n{e.stderr}")
# Save configuration for future use
if st.button("Save Configuration"):
save_config({
"selected_directories": selected_directories,
"excluded_files": excluded_files,
}, repo_path)
st.success("Configuration saved successfully.")
else:
st.write("Please select a folder to begin.")