harvesthealth commited on
Commit
9be5d6e
·
verified ·
1 Parent(s): 853441c

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +135 -24
  2. test_fetch_readme.py +47 -0
app.py CHANGED
@@ -713,7 +713,36 @@ async def handle_background_polling(mentor_history, planning_history):
713
 
714
  return updated_mentor, updated_planning
715
 
716
- async def handle_chat(message, history, persona="planning"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
  """Handle chat using run_manual_agent directly to support streaming tool calls."""
718
  all_tools = await get_all_tools(include_jules=False)
719
 
@@ -739,10 +768,15 @@ async def handle_chat(message, history, persona="planning"):
739
  base_system_message = MENTOR_SYSTEM_PROMPT if persona == "mentor" else PLANNING_SYSTEM_PROMPT
740
  p_state = json.dumps(initial_project_state)
741
 
 
 
 
 
742
  system_message = (
743
  f"{base_system_message}\n\n"
744
  f"Long-Term Memory/Context: {ltm_context}\n"
745
  f"Project State: {p_state}"
 
746
  )
747
 
748
  lc_messages = convert_to_langchain_messages(history)
@@ -768,7 +802,7 @@ async def handle_chat(message, history, persona="planning"):
768
  # Update memory at the end
769
  await update_memory(long_term_store, namespace, lc_messages + [AIMessage(content=final_output)], ltm_context)
770
 
771
- async def handle_close_ideate(history, persona="planning"):
772
  if not history:
773
  folders = get_ideation_logs()
774
  # Returns: chat_col, structured_col, desc, tasks, repos, expect, api, prof, space, token, status, dropdown, dropdown_comm
@@ -778,7 +812,7 @@ async def handle_close_ideate(history, persona="planning"):
778
 
779
  # 1. Extraction using alias-large (granular requests)
780
  fields_config = {
781
- "project_description": "Extract the Project Description, including the vision, concrete goals, future use cases, and potential integrations.",
782
  "tasks_tests": """Extract the Tasks and Tests following this EXACT 7-point template:
783
  1. Estimation of Project Scope from 1-10 and with a presentation of the core parts
784
  2. Project Description w/ vision for the project, concrete goals what it should be capable of, and future use cases, and future integrations into other projects,
@@ -804,7 +838,11 @@ async def handle_close_ideate(history, persona="planning"):
804
 
805
  for key, instruction in fields_config.items():
806
  try:
807
- prompt = f"{instruction}\n\nLOG:\n{chat_text}\n\nRespond ONLY with the extracted content for this field. Do not add conversational filler."
 
 
 
 
808
  logger.info(f"Extracting {key} for {persona}...")
809
  response = await ainvoke_with_retry(extraction_llm, prompt)
810
  content = response.content
@@ -829,6 +867,8 @@ async def handle_close_ideate(history, persona="planning"):
829
  f.write(str(content))
830
 
831
  save_file("chat_log.txt", chat_text)
 
 
832
  save_file("project_description.txt", extracted.get("project_description", ""))
833
  save_file("tasks_tests.txt", extracted.get("tasks_tests", ""))
834
  save_file("github_repos.txt", extracted.get("github_repos", ""))
@@ -843,6 +883,14 @@ async def handle_close_ideate(history, persona="planning"):
843
  }
844
  with open(os.path.join(folder_path, "hf_deployment_data.json"), "w") as f:
845
  json.dump(hf_data, f)
 
 
 
 
 
 
 
 
846
 
847
  folders = get_ideation_logs()
848
 
@@ -943,9 +991,16 @@ async def handle_github_prep(idea_description, log_file, target_repo_input, hf_p
943
  if os.path.exists(src):
944
  shutil.copy2(src, os.path.join(jules_temp_dir, fname))
945
 
946
- # Write bundled context as well for compatibility
 
 
 
 
 
 
 
947
  with open(os.path.join(jules_temp_dir, "project_context.md"), 'w') as f:
948
- f.write(context_md)
949
 
950
  # 5. Generate/Append to AGENTS.md
951
  existing_agents_content = ""
@@ -1455,8 +1510,17 @@ async def update_fields_from_log(log_file):
1455
  if "space_name" in params and not space: space = params["space_name"]
1456
 
1457
  token = params.get("hf_token", "")
1458
- target_repo = params.get("target_repo", "")
1459
- github_owner = params.get("github_owner", "JsonLord")
 
 
 
 
 
 
 
 
 
1460
  except: pass
1461
  else:
1462
  # Fallback to old file-based
@@ -1757,6 +1821,10 @@ with gr.Blocks() as demo:
1757
 
1758
  with gr.Tab("Mentor Session"):
1759
  with gr.Column(visible=True) as mentor_chat_col:
 
 
 
 
1760
  mentor_chatbot = gr.Chatbot()
1761
  mentor_msg = gr.Textbox(label="Enter your idea or question")
1762
  with gr.Row():
@@ -1780,15 +1848,19 @@ with gr.Blocks() as demo:
1780
 
1781
  mentor_status = gr.Textbox(label="Session Status", interactive=False)
1782
 
1783
- async def mentor_respond(message, chat_history):
1784
- async for updated_history in handle_chat(message, chat_history, persona="mentor"):
1785
  yield "", updated_history
1786
- mentor_submit_btn.click(mentor_respond, [mentor_msg, mentor_chatbot], [mentor_msg, mentor_chatbot])
1787
- mentor_msg.submit(mentor_respond, [mentor_msg, mentor_chatbot], [mentor_msg, mentor_chatbot])
1788
  mentor_back_btn.click(lambda: (gr.update(visible=True), gr.update(visible=False)), None, [mentor_chat_col, mentor_structured_col])
1789
 
1790
  with gr.Tab("Planning Session"):
1791
  with gr.Column(visible=True) as planning_chat_col:
 
 
 
 
1792
  chatbot = gr.Chatbot()
1793
  msg = gr.Textbox(label="Enter your idea or question")
1794
  with gr.Row():
@@ -1812,12 +1884,12 @@ with gr.Blocks() as demo:
1812
 
1813
  ideate_status = gr.Textbox(label="Session Status", interactive=False)
1814
 
1815
- async def respond(message, chat_history):
1816
- async for updated_history in handle_chat(message, chat_history, persona="planning"):
1817
  yield "", updated_history
1818
 
1819
- p_click = submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
1820
- p_submit = msg.submit(respond, [msg, chatbot], [msg, chatbot])
1821
  plan_back_btn.click(lambda: (gr.update(visible=True), gr.update(visible=False)), None, [planning_chat_col, planning_structured_col])
1822
 
1823
  with gr.Tab("Push Files") as push_files_tab:
@@ -1921,15 +1993,22 @@ with gr.Blocks() as demo:
1921
  nudge_btn.click(handle_supervisor_nudge, inputs=[mon_session_id, log_dropdown_comm], outputs=[nudge_output])
1922
 
1923
  # End Session Logic
1924
- async def mentor_close_wrap(h):
1925
- return await handle_close_ideate(h, persona="mentor")
1926
 
1927
- async def planning_close_wrap(h):
1928
- return await handle_close_ideate(h, persona="planning")
 
 
 
 
 
 
 
1929
 
1930
  mentor_close_btn.click(
1931
  mentor_close_wrap,
1932
- [mentor_chatbot],
1933
  [mentor_chat_col, mentor_structured_col, mentor_proj_desc, mentor_tasks_tests, mentor_repos, mentor_expectations, mentor_endpoints, mentor_prof, mentor_space, mentor_token, mentor_status, log_dropdown, log_dropdown_comm]
1934
  )
1935
 
@@ -1937,13 +2016,26 @@ with gr.Blocks() as demo:
1937
 
1938
  close_btn.click(
1939
  planning_close_wrap,
1940
- [chatbot],
1941
  planning_outputs
1942
  )
1943
 
1944
  # Planning session auto-pipeline (trigger close after final answer)
1945
- p_click.then(planning_close_wrap, [chatbot], planning_outputs)
1946
- p_submit.then(planning_close_wrap, [chatbot], planning_outputs)
 
 
 
 
 
 
 
 
 
 
 
 
 
1947
 
1948
  # Log Refresh Logic
1949
  refresh_logs_btn_1.click(refresh_logs_ui, outputs=[log_dropdown, log_dropdown_comm])
@@ -1969,6 +2061,25 @@ with gr.Blocks() as demo:
1969
  outputs=[repo_input]
1970
  ).then(fn=None, js="() => { document.querySelectorAll('button').forEach(b => { if(b.textContent.includes('Jules Communication')) b.click(); }); }")
1971
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1972
  with gr.Tab("Upload"):
1973
  hf_space_id_in = gr.Textbox(label="Hugging Face Space ID ({hf_profileID}/{spaceID})")
1974
  with gr.Row():
 
713
 
714
  return updated_mentor, updated_planning
715
 
716
+ async def fetch_readme(repo_url_input, branch="main"):
717
+ """Fetch README.md from a GitHub repository."""
718
+ if not repo_url_input:
719
+ return ""
720
+
721
+ owner, repo = parse_github_repo_id(repo_url_input)
722
+ if not repo:
723
+ return ""
724
+ if not owner:
725
+ owner = "JsonLord"
726
+
727
+ github_token = get_github_token(owner)
728
+ # Use raw content to avoid base64 decoding manually if possible, or just use the standard API
729
+ headers = {"Authorization": f"token {github_token}", "Accept": "application/vnd.github.v3.raw"}
730
+
731
+ # Try different common README names
732
+ for name in ["README.md", "README", "readme.md"]:
733
+ url = f"https://api.github.com/repos/{owner}/{repo}/contents/{name}?ref={branch}"
734
+ try:
735
+ response = requests.get(url, headers=headers)
736
+ if response.status_code == 200:
737
+ logger.info(f"Successfully fetched {name} for {owner}/{repo} (branch: {branch})")
738
+ return response.text
739
+ except Exception as e:
740
+ logger.error(f"Error fetching {name} for {owner}/{repo}: {e}")
741
+
742
+ logger.warning(f"Could not find README for {owner}/{repo} (branch: {branch})")
743
+ return ""
744
+
745
+ async def handle_chat(message, history, persona="planning", readme_content=""):
746
  """Handle chat using run_manual_agent directly to support streaming tool calls."""
747
  all_tools = await get_all_tools(include_jules=False)
748
 
 
768
  base_system_message = MENTOR_SYSTEM_PROMPT if persona == "mentor" else PLANNING_SYSTEM_PROMPT
769
  p_state = json.dumps(initial_project_state)
770
 
771
+ readme_context = ""
772
+ if readme_content:
773
+ readme_context = f"\n\n### ORIGINAL PROJECT CONTEXT (README.md)\nYou are helping to adapt and improve the following existing project:\n{readme_content}"
774
+
775
  system_message = (
776
  f"{base_system_message}\n\n"
777
  f"Long-Term Memory/Context: {ltm_context}\n"
778
  f"Project State: {p_state}"
779
+ f"{readme_context}"
780
  )
781
 
782
  lc_messages = convert_to_langchain_messages(history)
 
802
  # Update memory at the end
803
  await update_memory(long_term_store, namespace, lc_messages + [AIMessage(content=final_output)], ltm_context)
804
 
805
+ async def handle_close_ideate(history, persona="planning", readme_content="", repo_url="", branch="main"):
806
  if not history:
807
  folders = get_ideation_logs()
808
  # Returns: chat_col, structured_col, desc, tasks, repos, expect, api, prof, space, token, status, dropdown, dropdown_comm
 
812
 
813
  # 1. Extraction using alias-large (granular requests)
814
  fields_config = {
815
+ "project_description": "Based on the original README (if provided) and the chat log, extract the Project Description. Focus on how the project is being adapted and improved. Include the vision, concrete goals, future use cases, and potential integrations.",
816
  "tasks_tests": """Extract the Tasks and Tests following this EXACT 7-point template:
817
  1. Estimation of Project Scope from 1-10 and with a presentation of the core parts
818
  2. Project Description w/ vision for the project, concrete goals what it should be capable of, and future use cases, and future integrations into other projects,
 
838
 
839
  for key, instruction in fields_config.items():
840
  try:
841
+ readme_context = ""
842
+ if readme_content:
843
+ readme_context = f"ORIGINAL PROJECT README:\n{readme_content}\n\n"
844
+
845
+ prompt = f"{readme_context}{instruction}\n\nCHAT LOG:\n{chat_text}\n\nRespond ONLY with the extracted content for this field. Do not add conversational filler."
846
  logger.info(f"Extracting {key} for {persona}...")
847
  response = await ainvoke_with_retry(extraction_llm, prompt)
848
  content = response.content
 
867
  f.write(str(content))
868
 
869
  save_file("chat_log.txt", chat_text)
870
+ if readme_content:
871
+ save_file("original_readme.md", readme_content)
872
  save_file("project_description.txt", extracted.get("project_description", ""))
873
  save_file("tasks_tests.txt", extracted.get("tasks_tests", ""))
874
  save_file("github_repos.txt", extracted.get("github_repos", ""))
 
883
  }
884
  with open(os.path.join(folder_path, "hf_deployment_data.json"), "w") as f:
885
  json.dump(hf_data, f)
886
+
887
+ # Repo Info
888
+ repo_info = {
889
+ "repo_url": repo_url,
890
+ "branch": branch
891
+ }
892
+ with open(os.path.join(folder_path, "repo_info.json"), "w") as f:
893
+ json.dump(repo_info, f)
894
 
895
  folders = get_ideation_logs()
896
 
 
991
  if os.path.exists(src):
992
  shutil.copy2(src, os.path.join(jules_temp_dir, fname))
993
 
994
+ # Write Project_Context.md. Preference: original README, else bundled context.
995
+ final_context = context_md
996
+ readme_path = os.path.join(log_path, "original_readme.md")
997
+ if os.path.exists(readme_path):
998
+ with open(readme_path, "r") as f:
999
+ final_context = f.read()
1000
+ logger.info("Using original_readme.md for Project_Context.md")
1001
+
1002
  with open(os.path.join(jules_temp_dir, "project_context.md"), 'w') as f:
1003
+ f.write(final_context)
1004
 
1005
  # 5. Generate/Append to AGENTS.md
1006
  existing_agents_content = ""
 
1510
  if "space_name" in params and not space: space = params["space_name"]
1511
 
1512
  token = params.get("hf_token", "")
1513
+ except: pass
1514
+
1515
+ repo_info_path = os.path.join(log_path, "repo_info.json")
1516
+ if os.path.exists(repo_info_path):
1517
+ try:
1518
+ with open(repo_info_path, "r") as f:
1519
+ rparams = json.load(f)
1520
+ target_repo = rparams.get("repo_url", "")
1521
+ # We can also update the default owner if we parse it
1522
+ if "/" in target_repo:
1523
+ github_owner = target_repo.split("/")[-2]
1524
  except: pass
1525
  else:
1526
  # Fallback to old file-based
 
1821
 
1822
  with gr.Tab("Mentor Session"):
1823
  with gr.Column(visible=True) as mentor_chat_col:
1824
+ with gr.Row():
1825
+ mentor_repo_url = gr.Textbox(label="Project Repo (owner/repo or URL)", scale=3)
1826
+ mentor_branch = gr.Dropdown(label="Branch", choices=["main"], value="main", scale=1, allow_custom_value=True)
1827
+ mentor_readme_state = gr.State("")
1828
  mentor_chatbot = gr.Chatbot()
1829
  mentor_msg = gr.Textbox(label="Enter your idea or question")
1830
  with gr.Row():
 
1848
 
1849
  mentor_status = gr.Textbox(label="Session Status", interactive=False)
1850
 
1851
+ async def mentor_respond(message, chat_history, readme):
1852
+ async for updated_history in handle_chat(message, chat_history, persona="mentor", readme_content=readme):
1853
  yield "", updated_history
1854
+ mentor_submit_btn.click(mentor_respond, [mentor_msg, mentor_chatbot, mentor_readme_state], [mentor_msg, mentor_chatbot])
1855
+ mentor_msg.submit(mentor_respond, [mentor_msg, mentor_chatbot, mentor_readme_state], [mentor_msg, mentor_chatbot])
1856
  mentor_back_btn.click(lambda: (gr.update(visible=True), gr.update(visible=False)), None, [mentor_chat_col, mentor_structured_col])
1857
 
1858
  with gr.Tab("Planning Session"):
1859
  with gr.Column(visible=True) as planning_chat_col:
1860
+ with gr.Row():
1861
+ plan_repo_url = gr.Textbox(label="Project Repo (owner/repo or URL)", scale=3)
1862
+ plan_branch = gr.Dropdown(label="Branch", choices=["main"], value="main", scale=1, allow_custom_value=True)
1863
+ plan_readme_state = gr.State("")
1864
  chatbot = gr.Chatbot()
1865
  msg = gr.Textbox(label="Enter your idea or question")
1866
  with gr.Row():
 
1884
 
1885
  ideate_status = gr.Textbox(label="Session Status", interactive=False)
1886
 
1887
+ async def respond(message, chat_history, readme):
1888
+ async for updated_history in handle_chat(message, chat_history, persona="planning", readme_content=readme):
1889
  yield "", updated_history
1890
 
1891
+ p_click = submit_btn.click(respond, [msg, chatbot, plan_readme_state], [msg, chatbot])
1892
+ p_submit = msg.submit(respond, [msg, chatbot, plan_readme_state], [msg, chatbot])
1893
  plan_back_btn.click(lambda: (gr.update(visible=True), gr.update(visible=False)), None, [planning_chat_col, planning_structured_col])
1894
 
1895
  with gr.Tab("Push Files") as push_files_tab:
 
1993
  nudge_btn.click(handle_supervisor_nudge, inputs=[mon_session_id, log_dropdown_comm], outputs=[nudge_output])
1994
 
1995
  # End Session Logic
1996
+ async def mentor_close_wrap(h, readme):
1997
+ return await handle_close_ideate(h, persona="mentor", readme_content=readme)
1998
 
1999
+ async def planning_close_wrap(h, readme):
2000
+ return await handle_close_ideate(h, persona="planning", readme_content=readme)
2001
+
2002
+ # End Session Logic
2003
+ async def mentor_close_wrap(h, readme, repo, branch):
2004
+ return await handle_close_ideate(h, persona="mentor", readme_content=readme, repo_url=repo, branch=branch)
2005
+
2006
+ async def planning_close_wrap(h, readme, repo, branch):
2007
+ return await handle_close_ideate(h, persona="planning", readme_content=readme, repo_url=repo, branch=branch)
2008
 
2009
  mentor_close_btn.click(
2010
  mentor_close_wrap,
2011
+ [mentor_chatbot, mentor_readme_state, mentor_repo_url, mentor_branch],
2012
  [mentor_chat_col, mentor_structured_col, mentor_proj_desc, mentor_tasks_tests, mentor_repos, mentor_expectations, mentor_endpoints, mentor_prof, mentor_space, mentor_token, mentor_status, log_dropdown, log_dropdown_comm]
2013
  )
2014
 
 
2016
 
2017
  close_btn.click(
2018
  planning_close_wrap,
2019
+ [chatbot, plan_readme_state, plan_repo_url, plan_branch],
2020
  planning_outputs
2021
  )
2022
 
2023
  # Planning session auto-pipeline (trigger close after final answer)
2024
+ p_click.then(planning_close_wrap, [chatbot, plan_readme_state, plan_repo_url, plan_branch], planning_outputs)
2025
+ p_submit.then(planning_close_wrap, [chatbot, plan_readme_state, plan_repo_url, plan_branch], planning_outputs)
2026
+
2027
+ # Readme and Branch Loading Logic
2028
+ async def update_readme_state(repo, branch):
2029
+ content = await fetch_readme(repo, branch)
2030
+ return content
2031
+
2032
+ mentor_repo_url.change(load_github_branches, inputs=[mentor_repo_url], outputs=[mentor_branch])
2033
+ mentor_repo_url.change(update_readme_state, [mentor_repo_url, mentor_branch], [mentor_readme_state])
2034
+ mentor_branch.change(update_readme_state, [mentor_repo_url, mentor_branch], [mentor_readme_state])
2035
+
2036
+ plan_repo_url.change(load_github_branches, inputs=[plan_repo_url], outputs=[plan_branch])
2037
+ plan_repo_url.change(update_readme_state, [plan_repo_url, plan_branch], [plan_readme_state])
2038
+ plan_branch.change(update_readme_state, [plan_repo_url, plan_branch], [plan_readme_state])
2039
 
2040
  # Log Refresh Logic
2041
  refresh_logs_btn_1.click(refresh_logs_ui, outputs=[log_dropdown, log_dropdown_comm])
 
2061
  outputs=[repo_input]
2062
  ).then(fn=None, js="() => { document.querySelectorAll('button').forEach(b => { if(b.textContent.includes('Jules Communication')) b.click(); }); }")
2063
 
2064
+ # Synchronization Logic for Repo and Branch across all tabs
2065
+ def sync_repo_val(val):
2066
+ return val, val, val, val
2067
+
2068
+ def sync_branch_val(val):
2069
+ return val, val, val, val
2070
+
2071
+ # Sync Repo URL
2072
+ mentor_repo_url.change(sync_repo_val, [mentor_repo_url], [plan_repo_url, target_repo, repo_input, mentor_repo_url])
2073
+ plan_repo_url.change(sync_repo_val, [plan_repo_url], [mentor_repo_url, target_repo, repo_input, plan_repo_url])
2074
+ target_repo.change(sync_repo_val, [target_repo], [mentor_repo_url, plan_repo_url, repo_input, target_repo])
2075
+ repo_input.change(sync_repo_val, [repo_input], [mentor_repo_url, plan_repo_url, target_repo, repo_input])
2076
+
2077
+ # Sync Branch
2078
+ mentor_branch.change(sync_branch_val, [mentor_branch], [plan_branch, target_branch, branch_dropdown, mentor_branch])
2079
+ plan_branch.change(sync_branch_val, [plan_branch], [mentor_branch, target_branch, branch_dropdown, plan_branch])
2080
+ target_branch.change(sync_branch_val, [target_branch], [mentor_branch, plan_branch, branch_dropdown, target_branch])
2081
+ branch_dropdown.change(sync_branch_val, [branch_dropdown], [mentor_branch, plan_branch, target_branch, branch_dropdown])
2082
+
2083
  with gr.Tab("Upload"):
2084
  hf_space_id_in = gr.Textbox(label="Hugging Face Space ID ({hf_profileID}/{spaceID})")
2085
  with gr.Row():
test_fetch_readme.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import os
3
+ import requests
4
+
5
+ def parse_github_repo_id(input_str):
6
+ if not input_str: return "", ""
7
+ input_str = input_str.strip().strip("/")
8
+ if "github.com/" in input_str:
9
+ import re
10
+ match = re.search(r'github\.com/([^/\s]+)/([^/\s.]+)', input_str)
11
+ if match:
12
+ repo = match.group(2)
13
+ if repo.endswith(".git"): repo = repo[:-4]
14
+ return match.group(1), repo
15
+ if "/" in input_str:
16
+ parts = input_str.split("/")
17
+ if len(parts) >= 2:
18
+ repo = parts[-1]
19
+ if repo.endswith(".git"): repo = repo[:-4]
20
+ return parts[-2], repo
21
+ return "", input_str.replace(".git", "")
22
+
23
+ async def fetch_readme(repo_url_input, branch="main"):
24
+ owner, repo = parse_github_repo_id(repo_url_input)
25
+ headers = {"Accept": "application/vnd.github.v3.raw"}
26
+ token = os.environ.get("GITHUB_TOKEN")
27
+ if token:
28
+ headers["Authorization"] = f"token {token}"
29
+
30
+ url = f"https://api.github.com/repos/{owner}/{repo}/contents/README.md?ref={branch}"
31
+ print(f"Fetching {url}...")
32
+ response = requests.get(url, headers=headers)
33
+ if response.status_code == 200:
34
+ return response.text
35
+ else:
36
+ print(f"Status: {response.status_code}, Text: {response.text[:100]}")
37
+ return ""
38
+
39
+ async def main():
40
+ content = await fetch_readme("langchain-ai/langchain")
41
+ if content:
42
+ print("Success!")
43
+ else:
44
+ print("Failed.")
45
+
46
+ if __name__ == "__main__":
47
+ asyncio.run(main())