Spaces:
Paused
Paused
Upload folder using huggingface_hub
Browse files- app.py +64 -103
- requirements.txt +0 -1
app.py
CHANGED
|
@@ -78,7 +78,7 @@ def patch_tinytroupe():
|
|
| 78 |
# Modify the model call inside the loop
|
| 79 |
old_call = "response = self._raw_model_call(model, chat_api_params)"
|
| 80 |
new_call = """if parallel_retry:
|
| 81 |
-
|
| 82 |
response = self._raw_model_call_parallel(["alias-large", "alias-huge"], chat_api_params)
|
| 83 |
if isinstance(response, Exception):
|
| 84 |
raise response
|
|
@@ -87,10 +87,10 @@ def patch_tinytroupe():
|
|
| 87 |
content = content.replace(old_call, new_call)
|
| 88 |
|
| 89 |
# Update the 502 catch block
|
| 90 |
-
pattern = r"if isinstance\(e, openai\.APIStatusError\) and e\.status_code == 502 and isinstance\(self, HelmholtzBlabladorClient\):.*?except Exception as fallback_e:.*?
|
| 91 |
|
| 92 |
new_502_block = """if isinstance(e, openai.APIStatusError) and e.status_code == 502 and isinstance(self, HelmholtzBlabladorClient):
|
| 93 |
-
|
| 94 |
parallel_retry = True
|
| 95 |
time.sleep(35)"""
|
| 96 |
|
|
@@ -118,7 +118,7 @@ def setup_mkslides():
|
|
| 118 |
content = content.replace('requires-python = ">=3.13"', 'requires-python = ">=3.12"')
|
| 119 |
with open(pyproject_path, "w") as f:
|
| 120 |
f.write(content)
|
| 121 |
-
|
| 122 |
# Install dependencies and mkslides
|
| 123 |
subprocess.run(["pip", "install", "./external/mkslides"])
|
| 124 |
else:
|
|
@@ -126,13 +126,11 @@ def setup_mkslides():
|
|
| 126 |
|
| 127 |
setup_mkslides()
|
| 128 |
|
|
|
|
| 129 |
from github import Github, Auth
|
|
|
|
| 130 |
from openai import OpenAI
|
| 131 |
-
|
| 132 |
-
import uvicorn
|
| 133 |
-
import gradio as gr
|
| 134 |
-
from gradio_logsview import LogsView, LogsViewRunner
|
| 135 |
-
|
| 136 |
|
| 137 |
# Add external/TinyTroupe to sys.path
|
| 138 |
TINYTROUPE_PATH = os.path.join(os.getcwd(), "external", "TinyTroupe")
|
|
@@ -241,10 +239,10 @@ def get_repo_branches(repo_full_name, github_client=None):
|
|
| 241 |
# Fetch branches
|
| 242 |
branches = list(repo.get_branches())
|
| 243 |
add_log(f"Discovered {len(branches)} branches.")
|
| 244 |
-
|
| 245 |
# Use ThreadPool to fetch commit dates in parallel to be MUCH faster
|
| 246 |
branch_info = []
|
| 247 |
-
|
| 248 |
def fetch_branch_date(b):
|
| 249 |
try:
|
| 250 |
commit = repo.get_commit(b.commit.sha)
|
|
@@ -254,7 +252,7 @@ def get_repo_branches(repo_full_name, github_client=None):
|
|
| 254 |
date = commit.commit.author.date
|
| 255 |
elif commit.commit and commit.commit.committer:
|
| 256 |
date = commit.commit.committer.date
|
| 257 |
-
|
| 258 |
if not date:
|
| 259 |
date = datetime.min
|
| 260 |
return (b.name, date)
|
|
@@ -263,14 +261,14 @@ def get_repo_branches(repo_full_name, github_client=None):
|
|
| 263 |
|
| 264 |
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
|
| 265 |
branch_info = list(executor.map(fetch_branch_date, branches))
|
| 266 |
-
|
| 267 |
# Sort by date descending
|
| 268 |
branch_info.sort(key=lambda x: x[1], reverse=True)
|
| 269 |
result = [b[0] for b in branch_info]
|
| 270 |
-
|
| 271 |
if result:
|
| 272 |
add_log(f"Successfully sorted {len(result)} branches. Latest: {result[0]}")
|
| 273 |
-
|
| 274 |
return result
|
| 275 |
except Exception as e:
|
| 276 |
add_log(f"ERROR fetching branches: {e}")
|
|
@@ -443,7 +441,7 @@ def generate_persona_from_endpoint(theme, customer_profile):
|
|
| 443 |
if not name:
|
| 444 |
name_match = re.search(r"I am ([^,\.]+)", result)
|
| 445 |
name = name_match.group(1) if name_match else f"User_{uuid.uuid4().hex[:4]}"
|
| 446 |
-
|
| 447 |
return {
|
| 448 |
"name": name,
|
| 449 |
"minibio": result,
|
|
@@ -455,20 +453,20 @@ def generate_persona_from_endpoint(theme, customer_profile):
|
|
| 455 |
|
| 456 |
def generate_personas(theme, customer_profile, num_personas):
|
| 457 |
add_log(f"Generating {num_personas} personas...")
|
| 458 |
-
|
| 459 |
# Try the new endpoint first
|
| 460 |
final_personas = []
|
| 461 |
for i in range(int(num_personas)):
|
| 462 |
p = generate_persona_from_endpoint(theme, customer_profile)
|
| 463 |
if p:
|
| 464 |
final_personas.append(p)
|
| 465 |
-
|
| 466 |
if len(final_personas) == int(num_personas):
|
| 467 |
add_log("Successfully generated all personas from endpoint.")
|
| 468 |
return final_personas
|
| 469 |
-
|
| 470 |
add_log("Falling back to TinyTroupe logic for remaining personas...")
|
| 471 |
-
|
| 472 |
# Ensure alias-large is used
|
| 473 |
config_manager.update("model", "alias-large")
|
| 474 |
config_manager.update("reasoning_model", "alias-large")
|
|
@@ -609,7 +607,6 @@ def start_and_monitor_sessions(personas, tasks, url):
|
|
| 609 |
yield "Error: JULES_API_KEY not set.", ""
|
| 610 |
return
|
| 611 |
|
| 612 |
-
import requests # moved here as it was not globally imported
|
| 613 |
with open("jules_template.md", "r") as f:
|
| 614 |
template = f.read()
|
| 615 |
|
|
@@ -617,7 +614,7 @@ def start_and_monitor_sessions(personas, tasks, url):
|
|
| 617 |
for persona in personas:
|
| 618 |
# Generate unique report ID
|
| 619 |
report_id = str(uuid.uuid4())[:8]
|
| 620 |
-
|
| 621 |
# Format prompt
|
| 622 |
prompt = template.replace("{{persona_context}}", json.dumps(persona))
|
| 623 |
prompt = prompt.replace("{{tasks_list}}", json.dumps(tasks))
|
|
@@ -687,9 +684,9 @@ def get_reports_in_branch(repo_full_name, branch_name, filter_type=None):
|
|
| 687 |
try:
|
| 688 |
repo = gh.get_repo(repo_full_name)
|
| 689 |
add_log(f"Scanning branch {branch_name} for reports (filter: {filter_type})...")
|
| 690 |
-
|
| 691 |
exclude_files = {"jules_template.md", "readme.md", "contributing.md", "license.md"}
|
| 692 |
-
|
| 693 |
# Method 1: Check user_experience_reports directory
|
| 694 |
reports = []
|
| 695 |
try:
|
|
@@ -699,16 +696,16 @@ def get_reports_in_branch(repo_full_name, branch_name, filter_type=None):
|
|
| 699 |
if name.endswith(".md"):
|
| 700 |
filename = name.lower()
|
| 701 |
if filename in exclude_files: continue
|
| 702 |
-
|
| 703 |
# Optional filtering
|
| 704 |
if filter_type == "report" and "slide" in filename: continue
|
| 705 |
if filter_type == "slides" and "report" in filename: continue
|
| 706 |
-
|
| 707 |
path = f"user_experience_reports/{name}"
|
| 708 |
reports.append(path)
|
| 709 |
except:
|
| 710 |
pass
|
| 711 |
-
|
| 712 |
# Method 2: Recursive scan for ALL Markdown files
|
| 713 |
add_log("Deep scanning repository for all Markdown files...")
|
| 714 |
tree = repo.get_git_tree(branch_name, recursive=True).tree
|
|
@@ -718,14 +715,14 @@ def get_reports_in_branch(repo_full_name, branch_name, filter_type=None):
|
|
| 718 |
filename = os.path.basename(path).lower()
|
| 719 |
if filename in exclude_files:
|
| 720 |
continue
|
| 721 |
-
|
| 722 |
# Optional filtering
|
| 723 |
if filter_type == "report" and "slide" in filename: continue
|
| 724 |
if filter_type == "slides" and "report" in filename: continue
|
| 725 |
|
| 726 |
if path not in reports:
|
| 727 |
reports.append(path)
|
| 728 |
-
|
| 729 |
# Sort by relevance
|
| 730 |
def sort_key(path):
|
| 731 |
p_lower = path.lower()
|
|
@@ -733,20 +730,20 @@ def get_reports_in_branch(repo_full_name, branch_name, filter_type=None):
|
|
| 733 |
# Highest priority: specific report.md and slides.md in user_experience_reports
|
| 734 |
if filter_type == "report" and p_lower == "user_experience_reports/report.md": score -= 1000
|
| 735 |
if filter_type == "slides" and p_lower == "user_experience_reports/slides.md": score -= 1000
|
| 736 |
-
|
| 737 |
# High priority: other files in user_experience_reports
|
| 738 |
if "user_experience_reports" in p_lower: score -= 100
|
| 739 |
-
|
| 740 |
# Medium priority: keywords in filename
|
| 741 |
filename = os.path.basename(p_lower)
|
| 742 |
if "report" in filename: score -= 50
|
| 743 |
if "slide" in filename: score -= 30
|
| 744 |
if "ux" in filename: score -= 20
|
| 745 |
-
|
| 746 |
return (score, p_lower)
|
| 747 |
|
| 748 |
reports.sort(key=sort_key)
|
| 749 |
-
|
| 750 |
add_log(f"Discovered {len(reports)} potential Markdown files.")
|
| 751 |
return reports
|
| 752 |
except Exception as e:
|
|
@@ -775,7 +772,6 @@ def pull_report_from_pr(pr_url):
|
|
| 775 |
if not gh:
|
| 776 |
return "Error: GITHUB_TOKEN not set."
|
| 777 |
|
| 778 |
-
import requests # moved here as it was not globally imported
|
| 779 |
try:
|
| 780 |
# Extract repo and PR number from URL
|
| 781 |
match = re.search(r"github\.com/([^/]+/[^/]+)/pull/(\d+)", pr_url)
|
|
@@ -800,7 +796,7 @@ def pull_report_from_pr(pr_url):
|
|
| 800 |
return content
|
| 801 |
except:
|
| 802 |
return "Report not found yet in this branch."
|
| 803 |
-
|
| 804 |
# Get the first report found
|
| 805 |
content = get_report_content(repo_full_name, branch_name, reports[0])
|
| 806 |
processed_prs.add(pr_number)
|
|
@@ -815,11 +811,11 @@ def render_slides(repo_full_name, branch_name, report_path):
|
|
| 815 |
return "Error: GitHub client not initialized. Check your token."
|
| 816 |
if not repo_full_name or not branch_name or not report_path:
|
| 817 |
return "Please select a repository, branch, and report."
|
| 818 |
-
|
| 819 |
try:
|
| 820 |
repo = gh.get_repo(repo_full_name)
|
| 821 |
content = None
|
| 822 |
-
|
| 823 |
# Method 1: Check for multi-file slides folder
|
| 824 |
# We check this first if the report_path is in user_experience_reports or if it's default
|
| 825 |
if "user_experience_reports" in report_path:
|
|
@@ -830,7 +826,7 @@ def render_slides(repo_full_name, branch_name, report_path):
|
|
| 830 |
add_log(f"Multi-file slides folder found in branch {branch_name}. Merging...")
|
| 831 |
slide_files = [c for c in folder_contents if c.name.endswith(".md")]
|
| 832 |
slide_files.sort(key=lambda x: x.name)
|
| 833 |
-
|
| 834 |
merged_content = ""
|
| 835 |
for i, sf in enumerate(slide_files):
|
| 836 |
file_data = repo.get_contents(sf.path, ref=branch_name)
|
|
@@ -838,7 +834,7 @@ def render_slides(repo_full_name, branch_name, report_path):
|
|
| 838 |
if i > 0:
|
| 839 |
merged_content += "\n\n---\n\n"
|
| 840 |
merged_content += slide_text
|
| 841 |
-
|
| 842 |
content = merged_content
|
| 843 |
add_log(f"Successfully merged {len(slide_files)} slides.")
|
| 844 |
except:
|
|
@@ -878,29 +874,29 @@ def render_slides(repo_full_name, branch_name, report_path):
|
|
| 878 |
else:
|
| 879 |
add_log(f"Error fetching slides: {e}")
|
| 880 |
return f"Error fetching slides: {str(e)}"
|
| 881 |
-
|
| 882 |
# Prepare workspace
|
| 883 |
report_id = str(uuid.uuid4())[:8]
|
| 884 |
work_dir = f"slides_work_{report_id}"
|
| 885 |
os.makedirs(work_dir, exist_ok=True)
|
| 886 |
with open(f"{work_dir}/index.md", "w") as f:
|
| 887 |
f.write(content)
|
| 888 |
-
|
| 889 |
# Run mkslides
|
| 890 |
output_dir = f"slides_site_{report_id}"
|
| 891 |
# Ensure we have a clean output dir
|
| 892 |
if os.path.exists(output_dir):
|
| 893 |
shutil.rmtree(output_dir)
|
| 894 |
-
|
| 895 |
subprocess.run(["mkslides", "build", work_dir, "--site-dir", output_dir])
|
| 896 |
-
|
| 897 |
if os.path.exists(f"{output_dir}/index.html"):
|
| 898 |
-
# Return IFrame pointing to the generated site.
|
| 899 |
# We use /file= prefix which Gradio uses to serve files in allowed_paths.
|
| 900 |
return f'<iframe src="/file={os.path.abspath(output_dir)}/index.html" width="100%" height="600px"></iframe>'
|
| 901 |
else:
|
| 902 |
return "Failed to render slides."
|
| 903 |
-
|
| 904 |
except Exception as e:
|
| 905 |
print(f"Error rendering slides: {e}")
|
| 906 |
return f"Error rendering slides: {str(e)}"
|
|
@@ -918,7 +914,7 @@ def monitor_repo_for_reports():
|
|
| 918 |
new_content_found = False
|
| 919 |
for branch_name in branches[:25]: # Check top 25 recent branches
|
| 920 |
reports = get_reports_in_branch(REPO_NAME, branch_name, filter_type="report")
|
| 921 |
-
|
| 922 |
for report_file in reports:
|
| 923 |
report_key = f"{branch_name}/{report_file}"
|
| 924 |
if report_key not in processed_prs:
|
|
@@ -931,7 +927,7 @@ def monitor_repo_for_reports():
|
|
| 931 |
add_log(f"New report found: {report_file} in {branch_name}")
|
| 932 |
except:
|
| 933 |
continue
|
| 934 |
-
|
| 935 |
if not new_content_found:
|
| 936 |
add_log("No new reports found in recent branches.")
|
| 937 |
|
|
@@ -940,22 +936,6 @@ def monitor_repo_for_reports():
|
|
| 940 |
add_log(f"Error monitoring repo: {e}")
|
| 941 |
return all_discovered_reports
|
| 942 |
|
| 943 |
-
# Fast API setup
|
| 944 |
-
api_app = FastAPI()
|
| 945 |
-
|
| 946 |
-
@api_app.get("/health")
|
| 947 |
-
def health():
|
| 948 |
-
return {"status": "running", "timestamp": datetime.now().isoformat()}
|
| 949 |
-
|
| 950 |
-
@api_app.get("/api/repos")
|
| 951 |
-
def api_get_repos():
|
| 952 |
-
return {"repos": get_user_repos()}
|
| 953 |
-
|
| 954 |
-
@api_app.post("/api/generate_tasks")
|
| 955 |
-
def api_generate_tasks(theme: str, profile: str):
|
| 956 |
-
tasks = generate_tasks(theme, profile)
|
| 957 |
-
return {"tasks": tasks}
|
| 958 |
-
|
| 959 |
# Gradio UI
|
| 960 |
with gr.Blocks() as demo:
|
| 961 |
gr.Markdown("# Jules UX Analysis Orchestrator")
|
|
@@ -985,15 +965,15 @@ with gr.Blocks() as demo:
|
|
| 985 |
rv_repo_select = gr.Dropdown(label="Repository", choices=get_user_repos(), value=REPO_NAME)
|
| 986 |
rv_branch_select = gr.Dropdown(label="Branch", choices=get_repo_branches(REPO_NAME))
|
| 987 |
rv_refresh_branches_btn = gr.Button("Refresh Branches")
|
| 988 |
-
|
| 989 |
with gr.Row():
|
| 990 |
rv_report_select = gr.Dropdown(label="Select Report", choices=[], allow_custom_value=True)
|
| 991 |
rv_load_report_btn = gr.Button("Load Report")
|
| 992 |
-
|
| 993 |
rv_manual_path = gr.Textbox(label="Or enter manual path (e.g. docs/my_report.md)", placeholder="docs/my_report.md")
|
| 994 |
|
| 995 |
rv_report_viewer = gr.Markdown(label="Report Content")
|
| 996 |
-
|
| 997 |
def rv_update_branches(repo_name):
|
| 998 |
branches = get_repo_branches(repo_name)
|
| 999 |
latest = branches[0] if branches else "main"
|
|
@@ -1018,11 +998,11 @@ with gr.Blocks() as demo:
|
|
| 1018 |
sl_repo_select = gr.Dropdown(label="Repository", choices=get_user_repos(), value=REPO_NAME)
|
| 1019 |
sl_branch_select = gr.Dropdown(label="Branch", choices=get_repo_branches(REPO_NAME))
|
| 1020 |
sl_refresh_branches_btn = gr.Button("Refresh Branches")
|
| 1021 |
-
|
| 1022 |
with gr.Row():
|
| 1023 |
sl_report_select = gr.Dropdown(label="Select Report/Slides File", choices=[], allow_custom_value=True)
|
| 1024 |
sl_render_btn = gr.Button("Render Slideshow")
|
| 1025 |
-
|
| 1026 |
sl_manual_path = gr.Textbox(label="Or enter manual path (e.g. docs/slides.md)", placeholder="docs/slides.md")
|
| 1027 |
|
| 1028 |
slideshow_display = gr.HTML(label="Slideshow")
|
|
@@ -1046,17 +1026,14 @@ with gr.Blocks() as demo:
|
|
| 1046 |
sl_render_btn.click(fn=sl_render_wrapper, inputs=[sl_repo_select, sl_branch_select, sl_report_select, sl_manual_path], outputs=[slideshow_display])
|
| 1047 |
|
| 1048 |
with gr.Tab("System"):
|
| 1049 |
-
gr.Markdown("### System Diagnostics &
|
| 1050 |
-
|
| 1051 |
with gr.Row():
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
sys_status = gr.Textbox(label="Connection Status", interactive=False)
|
| 1059 |
-
sys_branch_output = gr.JSON(label="Discovered Branches")
|
| 1060 |
|
| 1061 |
def system_test(token, repo_name):
|
| 1062 |
global gh, GITHUB_TOKEN
|
|
@@ -1070,21 +1047,21 @@ with gr.Blocks() as demo:
|
|
| 1070 |
else:
|
| 1071 |
add_log("ERROR: No token provided and default client is missing.")
|
| 1072 |
return "Error: No GitHub client available. Please provide a token.", None
|
| 1073 |
-
|
| 1074 |
user = test_gh.get_user().login
|
| 1075 |
add_log(f"Successfully authenticated as {user}")
|
| 1076 |
-
|
| 1077 |
# Update global client if token was provided
|
| 1078 |
if token:
|
| 1079 |
gh = test_gh
|
| 1080 |
GITHUB_TOKEN = token
|
| 1081 |
add_log("Global GitHub client updated with new token.")
|
| 1082 |
-
|
| 1083 |
status = f"Success: Connected as {user} to {repo_name}"
|
| 1084 |
-
|
| 1085 |
# Use existing optimized logic
|
| 1086 |
branches = get_repo_branches(repo_name, github_client=test_gh)
|
| 1087 |
-
|
| 1088 |
return status, branches
|
| 1089 |
except Exception as e:
|
| 1090 |
add_log(f"System Test Error: {str(e)}")
|
|
@@ -1092,26 +1069,12 @@ with gr.Blocks() as demo:
|
|
| 1092 |
|
| 1093 |
sys_test_btn.click(fn=system_test, inputs=[sys_token_input, sys_repo_input], outputs=[sys_status, sys_branch_output])
|
| 1094 |
|
| 1095 |
-
gr.Markdown("#### Process Logs")
|
| 1096 |
-
logs_view = LogsView()
|
| 1097 |
-
|
| 1098 |
-
def run_diagnostics():
|
| 1099 |
-
runner = LogsViewRunner()
|
| 1100 |
-
yield from runner.run_python(lambda: print("Running System Diagnostics..."))
|
| 1101 |
-
yield from runner.run_python(lambda: print(f"GitHub Client: {'Initialized' if gh else 'Missing'}"))
|
| 1102 |
-
yield from runner.run_python(lambda: print(f"OS Path: {os.getcwd()}"))
|
| 1103 |
-
yield from runner.run_command(["pip", "show", "gradio"])
|
| 1104 |
-
yield runner.log("Diagnostics complete.")
|
| 1105 |
-
|
| 1106 |
-
diag_btn = gr.Button("Run Diagnostics & Show Logs")
|
| 1107 |
-
diag_btn.click(fn=run_diagnostics, outputs=logs_view)
|
| 1108 |
-
|
| 1109 |
with gr.Tab("Live Monitoring"):
|
| 1110 |
gr.Markdown("### Live Monitoring of JsonLord/tiny_web for new UX reports")
|
| 1111 |
live_log = gr.Textbox(label="GitHub Connection Logs", lines=5, interactive=False)
|
| 1112 |
refresh_feed_btn = gr.Button("Refresh Feed Now")
|
| 1113 |
global_feed = gr.Markdown(value="Waiting for new reports...")
|
| 1114 |
-
|
| 1115 |
def monitor_and_log():
|
| 1116 |
reports = monitor_repo_for_reports()
|
| 1117 |
logs = "\n".join(github_logs[-20:])
|
|
@@ -1135,8 +1098,6 @@ with gr.Blocks() as demo:
|
|
| 1135 |
outputs=[status_output, report_output]
|
| 1136 |
)
|
| 1137 |
|
| 1138 |
-
app = gr.mount_gradio_app(api_app, demo, path="/")
|
| 1139 |
-
|
| 1140 |
if __name__ == "__main__":
|
| 1141 |
# Startup connectivity check
|
| 1142 |
print("--- STARTUP GITHUB CONNECTIVITY CHECK ---")
|
|
@@ -1145,7 +1106,7 @@ if __name__ == "__main__":
|
|
| 1145 |
token_source = "GITHUB_TOKEN"
|
| 1146 |
elif os.environ.get("GITHUB_API_TOKEN"):
|
| 1147 |
token_source = "GITHUB_API_TOKEN"
|
| 1148 |
-
|
| 1149 |
print(f"Token Source: {token_source}")
|
| 1150 |
|
| 1151 |
if gh is None:
|
|
@@ -1154,7 +1115,7 @@ if __name__ == "__main__":
|
|
| 1154 |
try:
|
| 1155 |
user = gh.get_user().login
|
| 1156 |
print(f"SUCCESS: Logged in to GitHub as: {user}")
|
| 1157 |
-
|
| 1158 |
# Test branch fetching for REPO_NAME
|
| 1159 |
print(f"Testing branch fetch for {REPO_NAME}...")
|
| 1160 |
test_branches = get_repo_branches(REPO_NAME)
|
|
@@ -1164,4 +1125,4 @@ if __name__ == "__main__":
|
|
| 1164 |
print("-----------------------------------------")
|
| 1165 |
|
| 1166 |
# Allow current directory for file serving, specifically for slides_site_*
|
| 1167 |
-
|
|
|
|
| 78 |
# Modify the model call inside the loop
|
| 79 |
old_call = "response = self._raw_model_call(model, chat_api_params)"
|
| 80 |
new_call = """if parallel_retry:
|
| 81 |
+
logger.info("Attempting parallel call to alias-large and alias-huge.")
|
| 82 |
response = self._raw_model_call_parallel(["alias-large", "alias-huge"], chat_api_params)
|
| 83 |
if isinstance(response, Exception):
|
| 84 |
raise response
|
|
|
|
| 87 |
content = content.replace(old_call, new_call)
|
| 88 |
|
| 89 |
# Update the 502 catch block
|
| 90 |
+
pattern = r"if isinstance\(e, openai\.APIStatusError\) and e\.status_code == 502 and isinstance\(self, HelmholtzBlabladorClient\):.*?except Exception as fallback_e:.*?logger\.error\(f\"Fallback to OpenAI also failed: \{fallback_e\}\"\)"
|
| 91 |
|
| 92 |
new_502_block = """if isinstance(e, openai.APIStatusError) and e.status_code == 502 and isinstance(self, HelmholtzBlabladorClient):
|
| 93 |
+
logger.warning("Helmholtz API returned a 502 error. Waiting 35 seconds and enabling parallel retry...")
|
| 94 |
parallel_retry = True
|
| 95 |
time.sleep(35)"""
|
| 96 |
|
|
|
|
| 118 |
content = content.replace('requires-python = ">=3.13"', 'requires-python = ">=3.12"')
|
| 119 |
with open(pyproject_path, "w") as f:
|
| 120 |
f.write(content)
|
| 121 |
+
|
| 122 |
# Install dependencies and mkslides
|
| 123 |
subprocess.run(["pip", "install", "./external/mkslides"])
|
| 124 |
else:
|
|
|
|
| 126 |
|
| 127 |
setup_mkslides()
|
| 128 |
|
| 129 |
+
import gradio as gr
|
| 130 |
from github import Github, Auth
|
| 131 |
+
import requests
|
| 132 |
from openai import OpenAI
|
| 133 |
+
import logging
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
# Add external/TinyTroupe to sys.path
|
| 136 |
TINYTROUPE_PATH = os.path.join(os.getcwd(), "external", "TinyTroupe")
|
|
|
|
| 239 |
# Fetch branches
|
| 240 |
branches = list(repo.get_branches())
|
| 241 |
add_log(f"Discovered {len(branches)} branches.")
|
| 242 |
+
|
| 243 |
# Use ThreadPool to fetch commit dates in parallel to be MUCH faster
|
| 244 |
branch_info = []
|
| 245 |
+
|
| 246 |
def fetch_branch_date(b):
|
| 247 |
try:
|
| 248 |
commit = repo.get_commit(b.commit.sha)
|
|
|
|
| 252 |
date = commit.commit.author.date
|
| 253 |
elif commit.commit and commit.commit.committer:
|
| 254 |
date = commit.commit.committer.date
|
| 255 |
+
|
| 256 |
if not date:
|
| 257 |
date = datetime.min
|
| 258 |
return (b.name, date)
|
|
|
|
| 261 |
|
| 262 |
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
|
| 263 |
branch_info = list(executor.map(fetch_branch_date, branches))
|
| 264 |
+
|
| 265 |
# Sort by date descending
|
| 266 |
branch_info.sort(key=lambda x: x[1], reverse=True)
|
| 267 |
result = [b[0] for b in branch_info]
|
| 268 |
+
|
| 269 |
if result:
|
| 270 |
add_log(f"Successfully sorted {len(result)} branches. Latest: {result[0]}")
|
| 271 |
+
|
| 272 |
return result
|
| 273 |
except Exception as e:
|
| 274 |
add_log(f"ERROR fetching branches: {e}")
|
|
|
|
| 441 |
if not name:
|
| 442 |
name_match = re.search(r"I am ([^,\.]+)", result)
|
| 443 |
name = name_match.group(1) if name_match else f"User_{uuid.uuid4().hex[:4]}"
|
| 444 |
+
|
| 445 |
return {
|
| 446 |
"name": name,
|
| 447 |
"minibio": result,
|
|
|
|
| 453 |
|
| 454 |
def generate_personas(theme, customer_profile, num_personas):
|
| 455 |
add_log(f"Generating {num_personas} personas...")
|
| 456 |
+
|
| 457 |
# Try the new endpoint first
|
| 458 |
final_personas = []
|
| 459 |
for i in range(int(num_personas)):
|
| 460 |
p = generate_persona_from_endpoint(theme, customer_profile)
|
| 461 |
if p:
|
| 462 |
final_personas.append(p)
|
| 463 |
+
|
| 464 |
if len(final_personas) == int(num_personas):
|
| 465 |
add_log("Successfully generated all personas from endpoint.")
|
| 466 |
return final_personas
|
| 467 |
+
|
| 468 |
add_log("Falling back to TinyTroupe logic for remaining personas...")
|
| 469 |
+
|
| 470 |
# Ensure alias-large is used
|
| 471 |
config_manager.update("model", "alias-large")
|
| 472 |
config_manager.update("reasoning_model", "alias-large")
|
|
|
|
| 607 |
yield "Error: JULES_API_KEY not set.", ""
|
| 608 |
return
|
| 609 |
|
|
|
|
| 610 |
with open("jules_template.md", "r") as f:
|
| 611 |
template = f.read()
|
| 612 |
|
|
|
|
| 614 |
for persona in personas:
|
| 615 |
# Generate unique report ID
|
| 616 |
report_id = str(uuid.uuid4())[:8]
|
| 617 |
+
|
| 618 |
# Format prompt
|
| 619 |
prompt = template.replace("{{persona_context}}", json.dumps(persona))
|
| 620 |
prompt = prompt.replace("{{tasks_list}}", json.dumps(tasks))
|
|
|
|
| 684 |
try:
|
| 685 |
repo = gh.get_repo(repo_full_name)
|
| 686 |
add_log(f"Scanning branch {branch_name} for reports (filter: {filter_type})...")
|
| 687 |
+
|
| 688 |
exclude_files = {"jules_template.md", "readme.md", "contributing.md", "license.md"}
|
| 689 |
+
|
| 690 |
# Method 1: Check user_experience_reports directory
|
| 691 |
reports = []
|
| 692 |
try:
|
|
|
|
| 696 |
if name.endswith(".md"):
|
| 697 |
filename = name.lower()
|
| 698 |
if filename in exclude_files: continue
|
| 699 |
+
|
| 700 |
# Optional filtering
|
| 701 |
if filter_type == "report" and "slide" in filename: continue
|
| 702 |
if filter_type == "slides" and "report" in filename: continue
|
| 703 |
+
|
| 704 |
path = f"user_experience_reports/{name}"
|
| 705 |
reports.append(path)
|
| 706 |
except:
|
| 707 |
pass
|
| 708 |
+
|
| 709 |
# Method 2: Recursive scan for ALL Markdown files
|
| 710 |
add_log("Deep scanning repository for all Markdown files...")
|
| 711 |
tree = repo.get_git_tree(branch_name, recursive=True).tree
|
|
|
|
| 715 |
filename = os.path.basename(path).lower()
|
| 716 |
if filename in exclude_files:
|
| 717 |
continue
|
| 718 |
+
|
| 719 |
# Optional filtering
|
| 720 |
if filter_type == "report" and "slide" in filename: continue
|
| 721 |
if filter_type == "slides" and "report" in filename: continue
|
| 722 |
|
| 723 |
if path not in reports:
|
| 724 |
reports.append(path)
|
| 725 |
+
|
| 726 |
# Sort by relevance
|
| 727 |
def sort_key(path):
|
| 728 |
p_lower = path.lower()
|
|
|
|
| 730 |
# Highest priority: specific report.md and slides.md in user_experience_reports
|
| 731 |
if filter_type == "report" and p_lower == "user_experience_reports/report.md": score -= 1000
|
| 732 |
if filter_type == "slides" and p_lower == "user_experience_reports/slides.md": score -= 1000
|
| 733 |
+
|
| 734 |
# High priority: other files in user_experience_reports
|
| 735 |
if "user_experience_reports" in p_lower: score -= 100
|
| 736 |
+
|
| 737 |
# Medium priority: keywords in filename
|
| 738 |
filename = os.path.basename(p_lower)
|
| 739 |
if "report" in filename: score -= 50
|
| 740 |
if "slide" in filename: score -= 30
|
| 741 |
if "ux" in filename: score -= 20
|
| 742 |
+
|
| 743 |
return (score, p_lower)
|
| 744 |
|
| 745 |
reports.sort(key=sort_key)
|
| 746 |
+
|
| 747 |
add_log(f"Discovered {len(reports)} potential Markdown files.")
|
| 748 |
return reports
|
| 749 |
except Exception as e:
|
|
|
|
| 772 |
if not gh:
|
| 773 |
return "Error: GITHUB_TOKEN not set."
|
| 774 |
|
|
|
|
| 775 |
try:
|
| 776 |
# Extract repo and PR number from URL
|
| 777 |
match = re.search(r"github\.com/([^/]+/[^/]+)/pull/(\d+)", pr_url)
|
|
|
|
| 796 |
return content
|
| 797 |
except:
|
| 798 |
return "Report not found yet in this branch."
|
| 799 |
+
|
| 800 |
# Get the first report found
|
| 801 |
content = get_report_content(repo_full_name, branch_name, reports[0])
|
| 802 |
processed_prs.add(pr_number)
|
|
|
|
| 811 |
return "Error: GitHub client not initialized. Check your token."
|
| 812 |
if not repo_full_name or not branch_name or not report_path:
|
| 813 |
return "Please select a repository, branch, and report."
|
| 814 |
+
|
| 815 |
try:
|
| 816 |
repo = gh.get_repo(repo_full_name)
|
| 817 |
content = None
|
| 818 |
+
|
| 819 |
# Method 1: Check for multi-file slides folder
|
| 820 |
# We check this first if the report_path is in user_experience_reports or if it's default
|
| 821 |
if "user_experience_reports" in report_path:
|
|
|
|
| 826 |
add_log(f"Multi-file slides folder found in branch {branch_name}. Merging...")
|
| 827 |
slide_files = [c for c in folder_contents if c.name.endswith(".md")]
|
| 828 |
slide_files.sort(key=lambda x: x.name)
|
| 829 |
+
|
| 830 |
merged_content = ""
|
| 831 |
for i, sf in enumerate(slide_files):
|
| 832 |
file_data = repo.get_contents(sf.path, ref=branch_name)
|
|
|
|
| 834 |
if i > 0:
|
| 835 |
merged_content += "\n\n---\n\n"
|
| 836 |
merged_content += slide_text
|
| 837 |
+
|
| 838 |
content = merged_content
|
| 839 |
add_log(f"Successfully merged {len(slide_files)} slides.")
|
| 840 |
except:
|
|
|
|
| 874 |
else:
|
| 875 |
add_log(f"Error fetching slides: {e}")
|
| 876 |
return f"Error fetching slides: {str(e)}"
|
| 877 |
+
|
| 878 |
# Prepare workspace
|
| 879 |
report_id = str(uuid.uuid4())[:8]
|
| 880 |
work_dir = f"slides_work_{report_id}"
|
| 881 |
os.makedirs(work_dir, exist_ok=True)
|
| 882 |
with open(f"{work_dir}/index.md", "w") as f:
|
| 883 |
f.write(content)
|
| 884 |
+
|
| 885 |
# Run mkslides
|
| 886 |
output_dir = f"slides_site_{report_id}"
|
| 887 |
# Ensure we have a clean output dir
|
| 888 |
if os.path.exists(output_dir):
|
| 889 |
shutil.rmtree(output_dir)
|
| 890 |
+
|
| 891 |
subprocess.run(["mkslides", "build", work_dir, "--site-dir", output_dir])
|
| 892 |
+
|
| 893 |
if os.path.exists(f"{output_dir}/index.html"):
|
| 894 |
+
# Return IFrame pointing to the generated site.
|
| 895 |
# We use /file= prefix which Gradio uses to serve files in allowed_paths.
|
| 896 |
return f'<iframe src="/file={os.path.abspath(output_dir)}/index.html" width="100%" height="600px"></iframe>'
|
| 897 |
else:
|
| 898 |
return "Failed to render slides."
|
| 899 |
+
|
| 900 |
except Exception as e:
|
| 901 |
print(f"Error rendering slides: {e}")
|
| 902 |
return f"Error rendering slides: {str(e)}"
|
|
|
|
| 914 |
new_content_found = False
|
| 915 |
for branch_name in branches[:25]: # Check top 25 recent branches
|
| 916 |
reports = get_reports_in_branch(REPO_NAME, branch_name, filter_type="report")
|
| 917 |
+
|
| 918 |
for report_file in reports:
|
| 919 |
report_key = f"{branch_name}/{report_file}"
|
| 920 |
if report_key not in processed_prs:
|
|
|
|
| 927 |
add_log(f"New report found: {report_file} in {branch_name}")
|
| 928 |
except:
|
| 929 |
continue
|
| 930 |
+
|
| 931 |
if not new_content_found:
|
| 932 |
add_log("No new reports found in recent branches.")
|
| 933 |
|
|
|
|
| 936 |
add_log(f"Error monitoring repo: {e}")
|
| 937 |
return all_discovered_reports
|
| 938 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 939 |
# Gradio UI
|
| 940 |
with gr.Blocks() as demo:
|
| 941 |
gr.Markdown("# Jules UX Analysis Orchestrator")
|
|
|
|
| 965 |
rv_repo_select = gr.Dropdown(label="Repository", choices=get_user_repos(), value=REPO_NAME)
|
| 966 |
rv_branch_select = gr.Dropdown(label="Branch", choices=get_repo_branches(REPO_NAME))
|
| 967 |
rv_refresh_branches_btn = gr.Button("Refresh Branches")
|
| 968 |
+
|
| 969 |
with gr.Row():
|
| 970 |
rv_report_select = gr.Dropdown(label="Select Report", choices=[], allow_custom_value=True)
|
| 971 |
rv_load_report_btn = gr.Button("Load Report")
|
| 972 |
+
|
| 973 |
rv_manual_path = gr.Textbox(label="Or enter manual path (e.g. docs/my_report.md)", placeholder="docs/my_report.md")
|
| 974 |
|
| 975 |
rv_report_viewer = gr.Markdown(label="Report Content")
|
| 976 |
+
|
| 977 |
def rv_update_branches(repo_name):
|
| 978 |
branches = get_repo_branches(repo_name)
|
| 979 |
latest = branches[0] if branches else "main"
|
|
|
|
| 998 |
sl_repo_select = gr.Dropdown(label="Repository", choices=get_user_repos(), value=REPO_NAME)
|
| 999 |
sl_branch_select = gr.Dropdown(label="Branch", choices=get_repo_branches(REPO_NAME))
|
| 1000 |
sl_refresh_branches_btn = gr.Button("Refresh Branches")
|
| 1001 |
+
|
| 1002 |
with gr.Row():
|
| 1003 |
sl_report_select = gr.Dropdown(label="Select Report/Slides File", choices=[], allow_custom_value=True)
|
| 1004 |
sl_render_btn = gr.Button("Render Slideshow")
|
| 1005 |
+
|
| 1006 |
sl_manual_path = gr.Textbox(label="Or enter manual path (e.g. docs/slides.md)", placeholder="docs/slides.md")
|
| 1007 |
|
| 1008 |
slideshow_display = gr.HTML(label="Slideshow")
|
|
|
|
| 1026 |
sl_render_btn.click(fn=sl_render_wrapper, inputs=[sl_repo_select, sl_branch_select, sl_report_select, sl_manual_path], outputs=[slideshow_display])
|
| 1027 |
|
| 1028 |
with gr.Tab("System"):
|
| 1029 |
+
gr.Markdown("### System Diagnostics & Manual Connection")
|
|
|
|
| 1030 |
with gr.Row():
|
| 1031 |
+
sys_token_input = gr.Textbox(label="GitHub Token (Leave blank for default)", type="password")
|
| 1032 |
+
sys_repo_input = gr.Textbox(label="Repository (e.g., JsonLord/tiny_web)", value=REPO_NAME)
|
| 1033 |
+
sys_test_btn = gr.Button("Test Connection & Fetch Branches")
|
| 1034 |
+
|
| 1035 |
+
sys_status = gr.Textbox(label="Connection Status", interactive=False)
|
| 1036 |
+
sys_branch_output = gr.JSON(label="Discovered Branches")
|
|
|
|
|
|
|
| 1037 |
|
| 1038 |
def system_test(token, repo_name):
|
| 1039 |
global gh, GITHUB_TOKEN
|
|
|
|
| 1047 |
else:
|
| 1048 |
add_log("ERROR: No token provided and default client is missing.")
|
| 1049 |
return "Error: No GitHub client available. Please provide a token.", None
|
| 1050 |
+
|
| 1051 |
user = test_gh.get_user().login
|
| 1052 |
add_log(f"Successfully authenticated as {user}")
|
| 1053 |
+
|
| 1054 |
# Update global client if token was provided
|
| 1055 |
if token:
|
| 1056 |
gh = test_gh
|
| 1057 |
GITHUB_TOKEN = token
|
| 1058 |
add_log("Global GitHub client updated with new token.")
|
| 1059 |
+
|
| 1060 |
status = f"Success: Connected as {user} to {repo_name}"
|
| 1061 |
+
|
| 1062 |
# Use existing optimized logic
|
| 1063 |
branches = get_repo_branches(repo_name, github_client=test_gh)
|
| 1064 |
+
|
| 1065 |
return status, branches
|
| 1066 |
except Exception as e:
|
| 1067 |
add_log(f"System Test Error: {str(e)}")
|
|
|
|
| 1069 |
|
| 1070 |
sys_test_btn.click(fn=system_test, inputs=[sys_token_input, sys_repo_input], outputs=[sys_status, sys_branch_output])
|
| 1071 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1072 |
with gr.Tab("Live Monitoring"):
|
| 1073 |
gr.Markdown("### Live Monitoring of JsonLord/tiny_web for new UX reports")
|
| 1074 |
live_log = gr.Textbox(label="GitHub Connection Logs", lines=5, interactive=False)
|
| 1075 |
refresh_feed_btn = gr.Button("Refresh Feed Now")
|
| 1076 |
global_feed = gr.Markdown(value="Waiting for new reports...")
|
| 1077 |
+
|
| 1078 |
def monitor_and_log():
|
| 1079 |
reports = monitor_repo_for_reports()
|
| 1080 |
logs = "\n".join(github_logs[-20:])
|
|
|
|
| 1098 |
outputs=[status_output, report_output]
|
| 1099 |
)
|
| 1100 |
|
|
|
|
|
|
|
| 1101 |
if __name__ == "__main__":
|
| 1102 |
# Startup connectivity check
|
| 1103 |
print("--- STARTUP GITHUB CONNECTIVITY CHECK ---")
|
|
|
|
| 1106 |
token_source = "GITHUB_TOKEN"
|
| 1107 |
elif os.environ.get("GITHUB_API_TOKEN"):
|
| 1108 |
token_source = "GITHUB_API_TOKEN"
|
| 1109 |
+
|
| 1110 |
print(f"Token Source: {token_source}")
|
| 1111 |
|
| 1112 |
if gh is None:
|
|
|
|
| 1115 |
try:
|
| 1116 |
user = gh.get_user().login
|
| 1117 |
print(f"SUCCESS: Logged in to GitHub as: {user}")
|
| 1118 |
+
|
| 1119 |
# Test branch fetching for REPO_NAME
|
| 1120 |
print(f"Testing branch fetch for {REPO_NAME}...")
|
| 1121 |
test_branches = get_repo_branches(REPO_NAME)
|
|
|
|
| 1125 |
print("-----------------------------------------")
|
| 1126 |
|
| 1127 |
# Allow current directory for file serving, specifically for slides_site_*
|
| 1128 |
+
demo.launch(allowed_paths=[os.getcwd()])
|
requirements.txt
CHANGED
|
@@ -26,4 +26,3 @@ huggingface-hub
|
|
| 26 |
lxml_html_clean
|
| 27 |
beautifulsoup4
|
| 28 |
playwright
|
| 29 |
-
huggingface_hub==0.19.4
|
|
|
|
| 26 |
lxml_html_clean
|
| 27 |
beautifulsoup4
|
| 28 |
playwright
|
|
|