wony617 Claude commited on
Commit
d229b84
ยท
1 Parent(s): 1b1c0d8

refactor: fix multi-project GitHub PR targeting and improve error messages

Browse files

- Refactor GitHubPRAgent to accept repository parameters in constructor
- Fix PR creation to target correct repository based on selected project
- Update TocTreeHandler to support project-specific toctree files
- Improve error messages with specific project mismatch detection
- Pass project parameter from handler to ensure proper repository targeting

๐Ÿค– Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

agent/handler.py CHANGED
@@ -108,7 +108,7 @@ def process_file_search_handler(project: str, lang: str, k: int, history: list)
108
  2. Click "๐Ÿ’พ Save Configuration"
109
  3. Try "Find Files" again"""
110
  history.append(["File search request", response])
111
- return history, "", update_status(), gr.Tabs(selected=0), gr.update(choices=[])
112
  else:
113
  raise # Re-raise non-rate-limit errors
114
  state.files_to_translate = (
@@ -141,14 +141,13 @@ def process_file_search_handler(project: str, lang: str, k: int, history: list)
141
  # Add to history
142
  history.append(["Please find files that need translation", response])
143
  cleared_input = ""
144
- selected_tab = 1 if state.files_to_translate else 0
145
 
146
  # ๋“œ๋กญ๋‹ค์šด choices๋กœ ์“ธ ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜ ์ถ”๊ฐ€
147
  return (
148
  history,
149
  cleared_input,
150
  update_status(),
151
- gr.Tabs(selected=selected_tab),
152
  update_dropdown_choices(state.files_to_translate),
153
  )
154
 
@@ -157,7 +156,30 @@ def update_dropdown_choices(file_list):
157
  return gr.update(choices=file_list, value=None)
158
 
159
 
160
- def start_translation_process():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  """Start the translation process for the first file"""
162
  if not state.files_to_translate:
163
  return "โŒ No files available for translation.", ""
@@ -166,8 +188,8 @@ def start_translation_process():
166
 
167
  # Call translation function (simplified for demo)
168
  try:
169
- translated = translate_docs_interactive(
170
- state.target_language, [[current_file]], state.additional_instruction
171
  )
172
 
173
  state.current_file_content = {"translated": translated}
@@ -184,13 +206,17 @@ def start_translation_process():
184
  print("Compeleted translation:\n")
185
  print(translated)
186
  print("----------------------------")
187
- response = (
188
- f"""๐Ÿ”„ Translation for: `{current_file}`\n"""
189
- "**๐Ÿ“„ Original Content Link:**\n"
190
- ""
191
- f"{original_file_link}\n"
192
- "**๐ŸŒ Translated Content:**\n"
193
- )
 
 
 
 
194
  return response, translated
195
 
196
 
@@ -228,7 +254,12 @@ Currently available actions with quick controls:
228
  else:
229
  return """I understand you want to work on translations!
230
 
231
- To get started, please use the controls above to configure your translation settings and find files that need translation.
 
 
 
 
 
232
  """
233
 
234
 
@@ -325,7 +356,25 @@ def sync_language_displays(lang):
325
  return lang
326
 
327
 
328
- def update_persistent_config(anthropic_key, github_token, github_owner, github_repo, reference_pr_url):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  """Update persistent configuration settings."""
330
  global state
331
 
@@ -353,7 +402,15 @@ def update_persistent_config(anthropic_key, github_token, github_owner, github_r
353
  "reference_pr_url": reference_pr_url or "",
354
  })
355
 
356
- return f"โœ… Configuration saved! GitHub: {github_owner}/{github_repo}"
 
 
 
 
 
 
 
 
357
 
358
 
359
  def update_github_config(token, owner, repo, reference_pr_url):
@@ -374,7 +431,7 @@ def update_prompt_preview(language, file_path, additional_instruction):
374
  translation_lang = language
375
 
376
  # Get sample content (first 500 characters)
377
- content = get_content(file_path)
378
  to_translate = preprocess_content(content)
379
 
380
  # Truncate for preview
@@ -385,7 +442,10 @@ def update_prompt_preview(language, file_path, additional_instruction):
385
 
386
  return prompt
387
  except Exception as e:
388
- return f"Error generating prompt preview: {str(e)}"
 
 
 
389
 
390
 
391
  def send_message(message, history):
@@ -394,21 +454,39 @@ def send_message(message, history):
394
 
395
 
396
  # Button handlers with tab switching
397
- def start_translate_handler(history, file_to_translate, additional_instruction=""):
398
  # Use persistent anthropic key
399
  anthropic_key = state.persistent_settings["anthropic_api_key"]
400
  if not anthropic_key:
401
  response = "โŒ Please set Anthropic API key in Configuration panel first."
402
  history.append(["Translation request", response])
403
- return history, "", update_status(), gr.Tabs(selected=0)
404
 
405
  os.environ["ANTHROPIC_API_KEY"] = anthropic_key
406
 
 
 
 
 
 
 
407
  state.additional_instruction = additional_instruction
408
  state.files_to_translate = [file_to_translate]
409
- new_hist, cleared_input = handle_user_message("start translation", history)
410
- selected_tabs = 2 if state.current_file_content["translated"] else 0
411
- return new_hist, cleared_input, update_status(), gr.Tabs(selected=selected_tabs)
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
 
414
  def approve_handler(history, owner, repo, reference_pr_url):
@@ -416,15 +494,21 @@ def approve_handler(history, owner, repo, reference_pr_url):
416
  global state
417
  state.step = "create_github_pr"
418
 
419
- # Use persistent settings for token, update other values
420
  github_config = state.persistent_settings["github_config"]
 
 
421
  if not github_config.get("token"):
422
- response = "โŒ Please set GitHub Token in Configuration panel first."
423
- history.append(["GitHub PR creation request", response])
424
- return history, "", update_status()
425
-
426
- if not owner or not repo:
427
- response = "โŒ Please set GitHub Owner and Repository Name in Configuration panel first."
 
 
 
 
428
  history.append(["GitHub PR creation request", response])
429
  return history, "", update_status()
430
 
@@ -435,6 +519,9 @@ def approve_handler(history, owner, repo, reference_pr_url):
435
  # Use persistent settings
436
  github_config = state.persistent_settings["github_config"]
437
 
 
 
 
438
  # If reference PR is not provided, use the agent to find one
439
  if not github_config.get("reference_pr_url"):
440
  response = "๐Ÿค– **Reference PR URL not found. The agent will now search for a suitable one...**"
@@ -490,6 +577,7 @@ def approve_handler(history, owner, repo, reference_pr_url):
490
  translated_content=translated_content,
491
  github_config=state.github_config,
492
  en_title=file_name,
 
493
  )
494
  response += f"\n{pr_response}"
495
  else:
 
108
  2. Click "๐Ÿ’พ Save Configuration"
109
  3. Try "Find Files" again"""
110
  history.append(["File search request", response])
111
+ return history, "", update_status(), gr.Tabs(selected=0), gr.update(choices=[]), gr.update(visible=False)
112
  else:
113
  raise # Re-raise non-rate-limit errors
114
  state.files_to_translate = (
 
141
  # Add to history
142
  history.append(["Please find files that need translation", response])
143
  cleared_input = ""
 
144
 
145
  # ๋“œ๋กญ๋‹ค์šด choices๋กœ ์“ธ ํŒŒ์ผ ๋ฆฌ์ŠคํŠธ ๋ฐ˜ํ™˜ ์ถ”๊ฐ€
146
  return (
147
  history,
148
  cleared_input,
149
  update_status(),
150
+ gr.Tabs(), # Don't change tab
151
  update_dropdown_choices(state.files_to_translate),
152
  )
153
 
 
156
  return gr.update(choices=file_list, value=None)
157
 
158
 
159
+ def confirm_and_go_translate_handler(history):
160
+ """Confirm selection and go to translate tab"""
161
+ global state
162
+
163
+ response = f"โœ… **Selection confirmed!**\n\n๐ŸŽฏ **Project:** {state.selected_project}\n๐ŸŒ **Language:** {state.target_language}\n\n**โžก๏ธ Go to Tab 2 to start translation.**"
164
+ history.append(["Confirm selection", response])
165
+ return history, "", update_status(), gr.Tabs(selected=1)
166
+
167
+
168
+ def confirm_translation_and_go_upload_handler(history):
169
+ """Confirm translation and go to upload PR tab"""
170
+ global state
171
+
172
+ if not state.current_file_content.get("translated"):
173
+ response = "โŒ No translation available. Please complete translation first."
174
+ history.append(["Upload PR request", response])
175
+ return history, "", update_status(), gr.Tabs()
176
+
177
+ response = f"โœ… **Translation confirmed!**\n\n๐Ÿ“„ **File:** `{state.files_to_translate[0] if state.files_to_translate else 'Unknown'}`\n\n**โžก๏ธ Go to Tab 3 to upload PR.**"
178
+ history.append(["Upload PR request", response])
179
+ return history, "", update_status(), gr.Tabs(selected=2)
180
+
181
+
182
+ def start_translation_process(force_retranslate=False):
183
  """Start the translation process for the first file"""
184
  if not state.files_to_translate:
185
  return "โŒ No files available for translation.", ""
 
188
 
189
  # Call translation function (simplified for demo)
190
  try:
191
+ status, translated = translate_docs_interactive(
192
+ state.target_language, [[current_file]], state.additional_instruction, state.selected_project, force_retranslate
193
  )
194
 
195
  state.current_file_content = {"translated": translated}
 
206
  print("Compeleted translation:\n")
207
  print(translated)
208
  print("----------------------------")
209
+
210
+ # Different response format for existing vs new translation
211
+ if isinstance(status, str) and "Existing translation loaded" in status:
212
+ response = f"{status}\n**๐Ÿ“„ Original Content Link:** {original_file_link}\n\n**๐ŸŒ Translated Content:**"
213
+ else:
214
+ response = (
215
+ f"""๐Ÿ”„ Translation for: `{current_file}`\n"""
216
+ f"**๐Ÿ“„ Original Content Link:** {original_file_link}\n\n"
217
+ f"{status}\n\n"
218
+ "**๐ŸŒ Translated Content:**"
219
+ )
220
  return response, translated
221
 
222
 
 
254
  else:
255
  return """I understand you want to work on translations!
256
 
257
+ **Two ways to get started:**
258
+
259
+ 1. **๐Ÿ” Find Files first** - Use Tab 1 to discover files that need translation
260
+ 2. **๐Ÿš€ Direct Translation** - Go to Tab 2 and enter a file path directly (e.g., `docs/source/en/model_doc/bert.md`)
261
+
262
+ Make sure to configure your API keys in the Configuration panel above.
263
  """
264
 
265
 
 
356
  return lang
357
 
358
 
359
+ def update_project_selection(project, history):
360
+ """Update state when project is selected"""
361
+ global state
362
+ state.selected_project = project
363
+ response = f"Selection confirmed: ๐ŸŽฏ Project โ†’ **{project}**"
364
+ history.append(["Project selection", response])
365
+ return history, "", update_status()
366
+
367
+
368
+ def update_language_selection(lang, history):
369
+ """Update state when language is selected"""
370
+ global state
371
+ state.target_language = lang
372
+ response = f"Selection confirmed: ๐ŸŒ Language โ†’ **{lang}**"
373
+ history.append(["Language selection", response])
374
+ return history, "", update_status(), lang
375
+
376
+
377
+ def update_persistent_config(anthropic_key, github_token, github_owner, github_repo, reference_pr_url, history):
378
  """Update persistent configuration settings."""
379
  global state
380
 
 
402
  "reference_pr_url": reference_pr_url or "",
403
  })
404
 
405
+ # Build response message based on what was configured
406
+ response = "โœ… Configuration saved!"
407
+ if github_owner and github_repo:
408
+ response += f" GitHub: {github_owner}/{github_repo}"
409
+ elif anthropic_key:
410
+ response += " Anthropic API key updated."
411
+
412
+ history.append(["Configuration update", response])
413
+ return history, "", update_status()
414
 
415
 
416
  def update_github_config(token, owner, repo, reference_pr_url):
 
431
  translation_lang = language
432
 
433
  # Get sample content (first 500 characters)
434
+ content = get_content(file_path, state.selected_project)
435
  to_translate = preprocess_content(content)
436
 
437
  # Truncate for preview
 
442
 
443
  return prompt
444
  except Exception as e:
445
+ error_str = str(e)
446
+ if "Failed to retrieve content from the URL" in error_str:
447
+ return f"โŒ **File not found:** `{file_path}`\n\n๐Ÿ’ก **Please check:**\n1. Is this file in the **{state.selected_project}** project?\n2. Use \"๐Ÿ” Find Files to Translate\" to see available files\n3. Verify the file path is correct"
448
+ return f"Error generating prompt preview: {error_str}"
449
 
450
 
451
  def send_message(message, history):
 
454
 
455
 
456
  # Button handlers with tab switching
457
+ def start_translate_handler(history, file_to_translate, additional_instruction="", force_retranslate=False):
458
  # Use persistent anthropic key
459
  anthropic_key = state.persistent_settings["anthropic_api_key"]
460
  if not anthropic_key:
461
  response = "โŒ Please set Anthropic API key in Configuration panel first."
462
  history.append(["Translation request", response])
463
+ return history, "", update_status(), gr.Tabs(), gr.update(), gr.update()
464
 
465
  os.environ["ANTHROPIC_API_KEY"] = anthropic_key
466
 
467
+ # Check if file path is provided
468
+ if not file_to_translate or not file_to_translate.strip():
469
+ response = "โŒ Please select a file from the dropdown or enter a file path to translate."
470
+ history.append(["Translation request", response])
471
+ return history, "", update_status(), gr.Tabs(), gr.update(), gr.update()
472
+
473
  state.additional_instruction = additional_instruction
474
  state.files_to_translate = [file_to_translate]
475
+ state.step = "translate"
476
+
477
+ # Start translation directly
478
+ if force_retranslate:
479
+ history.append(["Translation request", "๐Ÿ”„ **Force retranslation started...**"])
480
+ response, translated = start_translation_process(force_retranslate)
481
+ history.append(["", response])
482
+ if translated:
483
+ history.append(["", translated])
484
+
485
+ # Update button text and show confirm button after translation
486
+ start_btn_text = "๐Ÿ”„ Retranslation" if state.current_file_content["translated"] else "๐Ÿš€ Start Translation"
487
+ confirm_btn_visible = bool(state.current_file_content["translated"])
488
+
489
+ return history, "", update_status(), gr.Tabs(), gr.update(value=start_btn_text), gr.update(visible=confirm_btn_visible)
490
 
491
 
492
  def approve_handler(history, owner, repo, reference_pr_url):
 
494
  global state
495
  state.step = "create_github_pr"
496
 
497
+ # Check all required GitHub configuration at once
498
  github_config = state.persistent_settings["github_config"]
499
+ missing_config = []
500
+
501
  if not github_config.get("token"):
502
+ missing_config.append("GitHub Token")
503
+ if not owner:
504
+ missing_config.append("GitHub Owner")
505
+ if not repo:
506
+ missing_config.append("Repository Name")
507
+
508
+ if missing_config:
509
+ config = get_project_config(state.selected_project)
510
+ repo_name = config.repo_url.split('/')[-1] # Extract repo name from URL
511
+ response = f"โŒ Please set the following in Configuration panel first: {', '.join(missing_config)}\n\n๐Ÿ’ก **Note:** GitHub Owner/Repository should be your fork of [`{repo_name}`]({config.repo_url}) (e.g., Owner: `your-username`, Repository: `{repo_name}`)"
512
  history.append(["GitHub PR creation request", response])
513
  return history, "", update_status()
514
 
 
519
  # Use persistent settings
520
  github_config = state.persistent_settings["github_config"]
521
 
522
+ # Initialize response variable
523
+ response = ""
524
+
525
  # If reference PR is not provided, use the agent to find one
526
  if not github_config.get("reference_pr_url"):
527
  response = "๐Ÿค– **Reference PR URL not found. The agent will now search for a suitable one...**"
 
577
  translated_content=translated_content,
578
  github_config=state.github_config,
579
  en_title=file_name,
580
+ project=state.selected_project,
581
  )
582
  response += f"\n{pr_response}"
583
  else:
agent/toctree_handler.py CHANGED
@@ -4,9 +4,17 @@ from typing import Dict, List, Any
4
  import os
5
 
6
  class TocTreeHandler:
7
- def __init__(self):
8
- self.en_toctree_url = "https://raw.githubusercontent.com/huggingface/transformers/main/docs/source/en/_toctree.yml"
9
- self.ko_toctree_url = "https://raw.githubusercontent.com/huggingface/transformers/main/docs/source/ko/_toctree.yml"
 
 
 
 
 
 
 
 
10
  self.local_docs_path = "docs/source/ko"
11
 
12
  def fetch_toctree(self, url: str) -> Dict[str, Any]:
@@ -245,7 +253,8 @@ Korean title:"""
245
  translation_result: dict,
246
  filepath: str,
247
  pr_agent,
248
- github_config: dict
 
249
  ) -> dict:
250
  """Update toctree after successful translation PR.
251
 
 
4
  import os
5
 
6
  class TocTreeHandler:
7
+ def __init__(self, project: str = "transformers"):
8
+ from translator.project_config import get_project_config
9
+ self.project = project
10
+ self.project_config = get_project_config(project)
11
+
12
+ # Extract repository path from config
13
+ repo_path = self.project_config.repo_url.replace("https://github.com/", "")
14
+
15
+ # Build project-specific URLs
16
+ self.en_toctree_url = f"https://raw.githubusercontent.com/{repo_path}/main/docs/source/en/_toctree.yml"
17
+ self.ko_toctree_url = f"https://raw.githubusercontent.com/{repo_path}/main/docs/source/ko/_toctree.yml"
18
  self.local_docs_path = "docs/source/ko"
19
 
20
  def fetch_toctree(self, url: str) -> Dict[str, Any]:
 
253
  translation_result: dict,
254
  filepath: str,
255
  pr_agent,
256
+ github_config: dict,
257
+ project: str = "transformers"
258
  ) -> dict:
259
  """Update toctree after successful translation PR.
260
 
agent/workflow.py CHANGED
@@ -62,23 +62,24 @@ def report_translation_target_files(
62
  return status_report, [[file] for file in filepath_list]
63
 
64
 
65
- def translate_docs(lang: str, file_path: str, additional_instruction: str = "") -> tuple[str, str]:
66
  """Translate documentation."""
67
- # Check if translation already exists
68
  translation_file_path = (
69
  Path(__file__).resolve().parent.parent
70
  / f"translation_result/{file_path}"
71
  )
72
 
73
- if translation_file_path.exists():
74
  print(f"๐Ÿ“„ Found existing translation: {translation_file_path}")
75
  with open(translation_file_path, "r", encoding="utf-8") as f:
76
  existing_content = f.read()
77
  if existing_content.strip():
78
- return "Existing translation loaded (no tokens used). If you want to translate again, please restart the gradio app.", existing_content
 
79
 
80
  # step 1. Get content from file path
81
- content = get_content(file_path)
82
  to_translate = preprocess_content(content)
83
 
84
  # step 2. Prepare prompt with docs content
@@ -101,7 +102,7 @@ def translate_docs(lang: str, file_path: str, additional_instruction: str = "")
101
 
102
 
103
  def translate_docs_interactive(
104
- translate_lang: str, selected_files: list[list[str]], additional_instruction: str = ""
105
  ) -> tuple[str, str]:
106
  """Interactive translation function that processes files one by one.
107
 
@@ -115,14 +116,22 @@ def translate_docs_interactive(
115
  # Start with the first file
116
  current_file = file_paths[0]
117
 
118
- status = f"โœ… Translation completed: `{current_file}` โ†’ `{translate_lang}`\n\n"
119
- callback_result, translated_content = translate_docs(translate_lang, current_file, additional_instruction)
120
- status += f"๐Ÿ’ฐ Used token and cost: \n```\n{callback_result}\n```"
 
 
 
 
 
 
 
 
121
 
122
  print(callback_result)
123
  print(status)
124
 
125
- return translated_content
126
 
127
 
128
  def generate_github_pr(
@@ -131,6 +140,7 @@ def generate_github_pr(
131
  translated_content: str = None,
132
  github_config: dict = None,
133
  en_title: str = None,
 
134
  ) -> str:
135
  """Generate a GitHub PR for translated documentation.
136
 
@@ -148,7 +158,7 @@ def generate_github_pr(
148
  return "โŒ GitHub PR Agent is not available. Please install required libraries."
149
 
150
  if not github_config:
151
- return "โŒ GitHub configuration not provided."
152
 
153
  # Validate required configuration
154
  required_fields = ["token", "owner", "repo_name", "reference_pr_url"]
@@ -157,7 +167,7 @@ def generate_github_pr(
157
  ]
158
 
159
  if missing_fields:
160
- return f"โŒ Missing required configuration: {', '.join(missing_fields)}. Please provide these values."
161
 
162
  # Set token in environment for the agent.
163
  os.environ["GITHUB_TOKEN"] = github_config["token"]
@@ -170,29 +180,39 @@ def generate_github_pr(
170
  / f"translation_result/{filepath}"
171
  )
172
  if not translation_file_path.exists():
173
- return f"โŒ Translation file not found: {translation_file_path}"
174
 
175
  with open(translation_file_path, "r", encoding="utf-8") as f:
176
  translated_content = f.read()
177
 
178
  if not translated_content or not translated_content.strip():
179
- return "โŒ Translated content is empty."
180
 
181
  # Execute GitHub PR Agent
 
 
 
 
 
 
182
  print(f"๐Ÿš€ Starting GitHub PR creation...")
183
  print(f" ๐Ÿ“ File: {filepath}")
184
  print(f" ๐ŸŒ Language: {target_language}")
185
  print(f" ๐Ÿ“Š Reference PR: {github_config['reference_pr_url']}")
186
- print(f" ๐Ÿ  Repository: {github_config['owner']}/{github_config['repo_name']}")
187
-
188
- agent = GitHubPRAgent()
 
 
 
 
 
 
189
  result = agent.run_translation_pr_workflow(
190
  reference_pr_url=github_config["reference_pr_url"],
191
  target_language=target_language,
192
  filepath=filepath,
193
  translated_doc=translated_content,
194
- owner=github_config["owner"],
195
- repo_name=github_config["repo_name"],
196
  base_branch=github_config.get("base_branch", "main"),
197
  )
198
  # TEST CODE
@@ -206,9 +226,9 @@ def generate_github_pr(
206
  toctree_result = None
207
  if en_title:
208
  from agent.toctree_handler import TocTreeHandler
209
- toctree_handler = TocTreeHandler()
210
  toctree_result = toctree_handler.update_toctree_after_translation(
211
- result, filepath, agent, github_config
212
  )
213
 
214
  # Process result
@@ -252,13 +272,29 @@ def generate_github_pr(
252
  {result.get("error_details", "Unknown error")}"""
253
 
254
  else:
 
255
  return f"""โŒ **GitHub PR Creation Failed**
256
 
257
  **Error Message:**
258
- {result["message"]}"""
 
 
 
 
 
 
 
 
259
 
260
  except Exception as e:
261
- error_msg = f"โŒ Unexpected error occurred during PR creation: {str(e)}"
 
 
 
 
 
 
 
262
  print(error_msg)
263
  return error_msg
264
 
 
62
  return status_report, [[file] for file in filepath_list]
63
 
64
 
65
+ def translate_docs(lang: str, file_path: str, additional_instruction: str = "", project: str = "transformers", force_retranslate: bool = False) -> tuple[str, str]:
66
  """Translate documentation."""
67
+ # Check if translation already exists (unless force retranslate is enabled)
68
  translation_file_path = (
69
  Path(__file__).resolve().parent.parent
70
  / f"translation_result/{file_path}"
71
  )
72
 
73
+ if not force_retranslate and translation_file_path.exists():
74
  print(f"๐Ÿ“„ Found existing translation: {translation_file_path}")
75
  with open(translation_file_path, "r", encoding="utf-8") as f:
76
  existing_content = f.read()
77
  if existing_content.strip():
78
+ existing_msg = f"โ™ป๏ธ **Existing translation loaded** (no tokens used)\n๐Ÿ“ **File:** `{file_path}`\n๐Ÿ“… **Loaded from:** `{translation_file_path}`\n๐Ÿ’ก **To retranslate:** Check 'Force Retranslate' option."
79
+ return existing_msg, existing_content
80
 
81
  # step 1. Get content from file path
82
+ content = get_content(file_path, project)
83
  to_translate = preprocess_content(content)
84
 
85
  # step 2. Prepare prompt with docs content
 
102
 
103
 
104
  def translate_docs_interactive(
105
+ translate_lang: str, selected_files: list[list[str]], additional_instruction: str = "", project: str = "transformers", force_retranslate: bool = False
106
  ) -> tuple[str, str]:
107
  """Interactive translation function that processes files one by one.
108
 
 
116
  # Start with the first file
117
  current_file = file_paths[0]
118
 
119
+ callback_result, translated_content = translate_docs(translate_lang, current_file, additional_instruction, project, force_retranslate)
120
+
121
+ # Check if existing translation was loaded
122
+ if isinstance(callback_result, str) and "Existing translation loaded" in callback_result:
123
+ status = callback_result # Use the existing translation message
124
+ else:
125
+ if force_retranslate:
126
+ status = f"๐Ÿ”„ **Force Retranslation completed**: `{current_file}` โ†’ `{translate_lang}`\n\n"
127
+ else:
128
+ status = f"โœ… Translation completed: `{current_file}` โ†’ `{translate_lang}`\n\n"
129
+ status += f"๐Ÿ’ฐ Used token and cost: \n```\n{callback_result}\n```"
130
 
131
  print(callback_result)
132
  print(status)
133
 
134
+ return status, translated_content
135
 
136
 
137
  def generate_github_pr(
 
140
  translated_content: str = None,
141
  github_config: dict = None,
142
  en_title: str = None,
143
+ project: str = "transformers",
144
  ) -> str:
145
  """Generate a GitHub PR for translated documentation.
146
 
 
158
  return "โŒ GitHub PR Agent is not available. Please install required libraries."
159
 
160
  if not github_config:
161
+ return "โŒ GitHub configuration not provided. Please set up GitHub token, owner, and repository in Configuration panel."
162
 
163
  # Validate required configuration
164
  required_fields = ["token", "owner", "repo_name", "reference_pr_url"]
 
167
  ]
168
 
169
  if missing_fields:
170
+ return f"โŒ Missing required GitHub configuration: {', '.join(missing_fields)}\n\n๐Ÿ’ก Go to Configuration panel and set:\n" + "\n".join([f" โ€ข {field}" for field in missing_fields])
171
 
172
  # Set token in environment for the agent.
173
  os.environ["GITHUB_TOKEN"] = github_config["token"]
 
180
  / f"translation_result/{filepath}"
181
  )
182
  if not translation_file_path.exists():
183
+ return f"โŒ Translation file not found: {translation_file_path}\n\n๐Ÿ’ก Please complete translation first in Tab 2 for file: {filepath}"
184
 
185
  with open(translation_file_path, "r", encoding="utf-8") as f:
186
  translated_content = f.read()
187
 
188
  if not translated_content or not translated_content.strip():
189
+ return f"โŒ Translated content is empty for file: {filepath}\n\n๐Ÿ’ก Please complete translation first in Tab 2."
190
 
191
  # Execute GitHub PR Agent
192
+ # Get base repository from project config
193
+ from translator.project_config import get_project_config
194
+ project_config = get_project_config(project)
195
+ base_repo_path = project_config.repo_url.replace("https://github.com/", "")
196
+ base_owner, base_repo = base_repo_path.split("/")
197
+
198
  print(f"๐Ÿš€ Starting GitHub PR creation...")
199
  print(f" ๐Ÿ“ File: {filepath}")
200
  print(f" ๐ŸŒ Language: {target_language}")
201
  print(f" ๐Ÿ“Š Reference PR: {github_config['reference_pr_url']}")
202
+ print(f" ๐Ÿ  User Fork: {github_config['owner']}/{github_config['repo_name']}")
203
+ print(f" ๐ŸŽฏ Base Repository: {base_owner}/{base_repo}")
204
+
205
+ agent = GitHubPRAgent(
206
+ user_owner=github_config["owner"],
207
+ user_repo=github_config["repo_name"],
208
+ base_owner=base_owner,
209
+ base_repo=base_repo,
210
+ )
211
  result = agent.run_translation_pr_workflow(
212
  reference_pr_url=github_config["reference_pr_url"],
213
  target_language=target_language,
214
  filepath=filepath,
215
  translated_doc=translated_content,
 
 
216
  base_branch=github_config.get("base_branch", "main"),
217
  )
218
  # TEST CODE
 
226
  toctree_result = None
227
  if en_title:
228
  from agent.toctree_handler import TocTreeHandler
229
+ toctree_handler = TocTreeHandler(project)
230
  toctree_result = toctree_handler.update_toctree_after_translation(
231
+ result, filepath, agent, github_config, project
232
  )
233
 
234
  # Process result
 
272
  {result.get("error_details", "Unknown error")}"""
273
 
274
  else:
275
+ error_details = result.get("error_details", "No additional details")
276
  return f"""โŒ **GitHub PR Creation Failed**
277
 
278
  **Error Message:**
279
+ {result["message"]}
280
+
281
+ **Error Details:**
282
+ {error_details}
283
+
284
+ ๐Ÿ’ก **Common Solutions:**
285
+ 1. **Project Mismatch**: Selected project '{project}' but fork is '{github_config.get('repo_name', 'REPO')}' - ensure they match
286
+ 2. Check if your GitHub fork exists: {github_config.get('owner', 'USER')}/{github_config.get('repo_name', 'REPO')}
287
+ 3. Verify GitHub token has write access to your fork"""
288
 
289
  except Exception as e:
290
+ error_msg = f"""โŒ **Unexpected Error During PR Creation**
291
+
292
+ **Error:** {str(e)}
293
+
294
+ **Configuration:**
295
+ โ€ข Project: {project}
296
+ โ€ข File: {filepath}
297
+ โ€ข Target: {github_config.get('owner', 'USER')}/{github_config.get('repo_name', 'REPO')} โ†’ {base_owner if 'base_owner' in locals() else 'BASE'}/{base_repo if 'base_repo' in locals() else 'REPO'}"""
298
  print(error_msg)
299
  return error_msg
300
 
app.py CHANGED
@@ -8,12 +8,16 @@ from dotenv import load_dotenv
8
 
9
  from agent.handler import (
10
  approve_handler,
 
 
11
  get_welcome_message,
12
  process_file_search_handler,
13
  restart_handler,
14
  send_message,
15
  start_translate_handler,
16
  sync_language_displays,
 
 
17
  update_prompt_preview,
18
  update_status,
19
  update_github_config,
@@ -188,6 +192,11 @@ with gr.Blocks(
188
  "๐Ÿ” Find Files to Translate",
189
  elem_classes="action-button",
190
  )
 
 
 
 
 
191
 
192
  with gr.TabItem("2. Translate", id=1):
193
  with gr.Group():
@@ -214,9 +223,13 @@ with gr.Blocks(
214
  lines=2,
215
  )
216
 
217
- with gr.Accordion("๐Ÿ” Preview Prompt", open=False):
 
 
 
 
 
218
  prompt_preview = gr.Textbox(
219
- label="Current Translation Prompt",
220
  lines=8,
221
  interactive=False,
222
  placeholder="Select a file and language to see the prompt preview...",
@@ -226,6 +239,12 @@ with gr.Blocks(
226
  start_translate_btn = gr.Button(
227
  "๐Ÿš€ Start Translation", elem_classes="action-button"
228
  )
 
 
 
 
 
 
229
 
230
  with gr.TabItem("3. Upload PR", id=2):
231
  with gr.Group():
@@ -247,12 +266,31 @@ with gr.Blocks(
247
  inputs=[project_dropdown, lang_dropdown, k_input, chatbot],
248
  outputs=[chatbot, msg_input, status_display, control_tabs, files_to_translate],
249
  )
 
 
 
 
 
 
250
 
251
- # Sync language across tabs
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  lang_dropdown.change(
253
- fn=sync_language_displays,
254
- inputs=[lang_dropdown],
255
- outputs=[translate_lang_display],
256
  )
257
 
258
  #
@@ -265,15 +303,21 @@ with gr.Blocks(
265
  # Button event handlers
266
  start_translate_btn.click(
267
  fn=start_translate_handler,
268
- inputs=[chatbot, file_to_translate_input, additional_instruction],
 
 
 
 
 
 
269
  outputs=[chatbot, msg_input, status_display, control_tabs],
270
  )
271
 
272
  # Configuration Save
273
  save_config_btn.click(
274
  fn=update_persistent_config,
275
- inputs=[config_anthropic_key, config_github_token, config_github_owner, config_github_repo, reference_pr_url],
276
- outputs=[msg_input],
277
  )
278
 
279
  approve_btn.click(
 
8
 
9
  from agent.handler import (
10
  approve_handler,
11
+ confirm_and_go_translate_handler,
12
+ confirm_translation_and_go_upload_handler,
13
  get_welcome_message,
14
  process_file_search_handler,
15
  restart_handler,
16
  send_message,
17
  start_translate_handler,
18
  sync_language_displays,
19
+ update_language_selection,
20
+ update_project_selection,
21
  update_prompt_preview,
22
  update_status,
23
  update_github_config,
 
192
  "๐Ÿ” Find Files to Translate",
193
  elem_classes="action-button",
194
  )
195
+
196
+ confirm_go_btn = gr.Button(
197
+ "โœ… Confirm Selection & Go to Translate",
198
+ elem_classes="action-button",
199
+ )
200
 
201
  with gr.TabItem("2. Translate", id=1):
202
  with gr.Group():
 
223
  lines=2,
224
  )
225
 
226
+ force_retranslate = gr.Checkbox(
227
+ label="๐Ÿ”„ Force Retranslate (ignore existing translations)",
228
+ value=False,
229
+ )
230
+
231
+ with gr.Accordion("๐Ÿ” Preview Translation Prompt", open=False):
232
  prompt_preview = gr.Textbox(
 
233
  lines=8,
234
  interactive=False,
235
  placeholder="Select a file and language to see the prompt preview...",
 
239
  start_translate_btn = gr.Button(
240
  "๐Ÿš€ Start Translation", elem_classes="action-button"
241
  )
242
+
243
+ confirm_upload_btn = gr.Button(
244
+ "โœ… Confirm Translation & Upload PR",
245
+ elem_classes="action-button",
246
+ visible=False,
247
+ )
248
 
249
  with gr.TabItem("3. Upload PR", id=2):
250
  with gr.Group():
 
266
  inputs=[project_dropdown, lang_dropdown, k_input, chatbot],
267
  outputs=[chatbot, msg_input, status_display, control_tabs, files_to_translate],
268
  )
269
+
270
+ confirm_go_btn.click(
271
+ fn=confirm_and_go_translate_handler,
272
+ inputs=[chatbot],
273
+ outputs=[chatbot, msg_input, status_display, control_tabs],
274
+ )
275
 
276
+ # Auto-save selections to state and update prompt preview
277
+ project_dropdown.change(
278
+ fn=update_project_selection,
279
+ inputs=[project_dropdown, chatbot],
280
+ outputs=[chatbot, msg_input, status_display],
281
+ )
282
+
283
+ # Update prompt preview when project changes
284
+ project_dropdown.change(
285
+ fn=update_prompt_preview,
286
+ inputs=[translate_lang_display, file_to_translate_input, additional_instruction],
287
+ outputs=[prompt_preview],
288
+ )
289
+
290
  lang_dropdown.change(
291
+ fn=update_language_selection,
292
+ inputs=[lang_dropdown, chatbot],
293
+ outputs=[chatbot, msg_input, status_display, translate_lang_display],
294
  )
295
 
296
  #
 
303
  # Button event handlers
304
  start_translate_btn.click(
305
  fn=start_translate_handler,
306
+ inputs=[chatbot, file_to_translate_input, additional_instruction, force_retranslate],
307
+ outputs=[chatbot, msg_input, status_display, control_tabs, start_translate_btn, confirm_upload_btn],
308
+ )
309
+
310
+ confirm_upload_btn.click(
311
+ fn=confirm_translation_and_go_upload_handler,
312
+ inputs=[chatbot],
313
  outputs=[chatbot, msg_input, status_display, control_tabs],
314
  )
315
 
316
  # Configuration Save
317
  save_config_btn.click(
318
  fn=update_persistent_config,
319
+ inputs=[config_anthropic_key, config_github_token, config_github_owner, config_github_repo, reference_pr_url, chatbot],
320
+ outputs=[chatbot, msg_input, status_display],
321
  )
322
 
323
  approve_btn.click(
pr_generator/agent.py CHANGED
@@ -34,9 +34,13 @@ except ImportError as e:
34
  class GitHubPRAgent:
35
  """Agent class for GitHub PR creation"""
36
 
37
- def __init__(self):
38
  self._github_client = None
39
  self._llm = None
 
 
 
 
40
 
41
  @property
42
  def github_client(self) -> Optional[Github]:
@@ -433,8 +437,6 @@ Please return only the commit message. No other explanation is needed."""
433
  target_language: str,
434
  filepath: str,
435
  translated_doc: str,
436
- owner: str,
437
- repo_name: str,
438
  base_branch: str = "main",
439
  ) -> Dict[str, Any]:
440
  """Execute translation document PR creation workflow."""
@@ -458,19 +460,20 @@ Please return only the commit message. No other explanation is needed."""
458
  )
459
 
460
  # 3. Get main branch SHA from upstream and create branch in fork
461
- upstream_repo = self.github_client.get_repo(f"huggingface/{repo_name}")
462
  main_branch = upstream_repo.get_branch(base_branch)
463
  main_sha = main_branch.commit.sha
464
 
465
  print(f"๐ŸŒฟ Creating branch: {branch_name} in fork repository")
466
- branch_result = self.create_branch(owner, repo_name, branch_name, main_sha)
467
 
468
  # Check branch creation result
469
  if branch_result.startswith("ERROR"):
470
  return {
471
  "status": "error",
472
- "message": f"Branch creation failed: {branch_result}",
473
  "branch": branch_name,
 
474
  }
475
  elif branch_result.startswith("WARNING"):
476
  print(f"โš ๏ธ {branch_result}")
@@ -489,8 +492,8 @@ Please return only the commit message. No other explanation is needed."""
489
 
490
  print(f"๐Ÿ“„ Saving file: {target_filepath}")
491
  file_result = self.create_or_update_file(
492
- owner,
493
- repo_name,
494
  target_filepath,
495
  commit_message,
496
  translated_doc,
@@ -500,9 +503,10 @@ Please return only the commit message. No other explanation is needed."""
500
  if not file_result.startswith("SUCCESS"):
501
  return {
502
  "status": "error",
503
- "message": "An issue occurred while saving the file.",
504
  "branch": branch_name,
505
  "file_path": target_filepath,
 
506
  }
507
 
508
  print(f"{file_result}")
@@ -518,11 +522,11 @@ Please return only the commit message. No other explanation is needed."""
518
  )
519
 
520
  print(f"๐Ÿ”„ Creating PR: {pr_title}")
521
- print(f" Head: {owner}:{branch_name} โ†’ Base: huggingface:{base_branch}")
522
 
523
  # Create PR from fork to upstream repository
524
  pr_result = self.create_pull_request(
525
- "huggingface", "transformers", pr_title, f"{owner}:{branch_name}", base_branch, pr_body, draft=True
526
  )
527
 
528
  if pr_result.startswith("ERROR"):
@@ -554,7 +558,8 @@ Please return only the commit message. No other explanation is needed."""
554
  except Exception as e:
555
  return {
556
  "status": "error",
557
- "message": f"Error occurred during workflow execution: {str(e)}",
 
558
  }
559
 
560
 
 
34
  class GitHubPRAgent:
35
  """Agent class for GitHub PR creation"""
36
 
37
+ def __init__(self, user_owner: str = None, user_repo: str = None, base_owner: str = None, base_repo: str = None):
38
  self._github_client = None
39
  self._llm = None
40
+ self.user_owner = user_owner
41
+ self.user_repo = user_repo
42
+ self.base_owner = base_owner
43
+ self.base_repo = base_repo
44
 
45
  @property
46
  def github_client(self) -> Optional[Github]:
 
437
  target_language: str,
438
  filepath: str,
439
  translated_doc: str,
 
 
440
  base_branch: str = "main",
441
  ) -> Dict[str, Any]:
442
  """Execute translation document PR creation workflow."""
 
460
  )
461
 
462
  # 3. Get main branch SHA from upstream and create branch in fork
463
+ upstream_repo = self.github_client.get_repo(f"{self.base_owner}/{self.base_repo}")
464
  main_branch = upstream_repo.get_branch(base_branch)
465
  main_sha = main_branch.commit.sha
466
 
467
  print(f"๐ŸŒฟ Creating branch: {branch_name} in fork repository")
468
+ branch_result = self.create_branch(self.user_owner, self.user_repo, branch_name, main_sha)
469
 
470
  # Check branch creation result
471
  if branch_result.startswith("ERROR"):
472
  return {
473
  "status": "error",
474
+ "message": f"Branch creation failed: {branch_result}\n\nTarget: {self.user_owner}/{self.user_repo}\nBranch: {branch_name}\nBase SHA: {main_sha[:8]}",
475
  "branch": branch_name,
476
+ "error_details": branch_result,
477
  }
478
  elif branch_result.startswith("WARNING"):
479
  print(f"โš ๏ธ {branch_result}")
 
492
 
493
  print(f"๐Ÿ“„ Saving file: {target_filepath}")
494
  file_result = self.create_or_update_file(
495
+ self.user_owner,
496
+ self.user_repo,
497
  target_filepath,
498
  commit_message,
499
  translated_doc,
 
503
  if not file_result.startswith("SUCCESS"):
504
  return {
505
  "status": "error",
506
+ "message": f"File save failed: {file_result}\n\n๐ŸŽฏ Target: {self.user_owner}/{self.user_repo} (expected: {target_language} fork of {self.base_owner}/{self.base_repo})\n๐ŸŒฟ Branch: {branch_name}\n๐Ÿ“ File: {target_filepath}",
507
  "branch": branch_name,
508
  "file_path": target_filepath,
509
+ "error_details": file_result,
510
  }
511
 
512
  print(f"{file_result}")
 
522
  )
523
 
524
  print(f"๐Ÿ”„ Creating PR: {pr_title}")
525
+ print(f" Head: {self.user_owner}:{branch_name} โ†’ Base: {self.base_owner}:{base_branch}")
526
 
527
  # Create PR from fork to upstream repository
528
  pr_result = self.create_pull_request(
529
+ self.base_owner, self.base_repo, pr_title, f"{self.user_owner}:{branch_name}", base_branch, pr_body, draft=True
530
  )
531
 
532
  if pr_result.startswith("ERROR"):
 
558
  except Exception as e:
559
  return {
560
  "status": "error",
561
+ "message": f"Workflow execution failed: {str(e)}\n\nConfig: {self.user_owner}/{self.user_repo} โ†’ {self.base_owner}/{self.base_repo}\nFile: {filepath if 'filepath' in locals() else 'Unknown'}",
562
+ "error_details": str(e),
563
  }
564
 
565
 
translator/content.py CHANGED
@@ -6,15 +6,18 @@ from langchain.callbacks import get_openai_callback
6
  from langchain_anthropic import ChatAnthropic
7
 
8
  from translator.prompt_glossary import PROMPT_WITH_GLOSSARY
 
9
 
10
 
11
- def get_content(filepath: str) -> str:
12
  if filepath == "":
13
  raise ValueError("No files selected for translation.")
14
 
15
- url = string.Template(
16
- "https://raw.githubusercontent.com/huggingface/" "transformers/main/$filepath"
17
- ).safe_substitute(filepath=filepath)
 
 
18
  response = requests.get(url)
19
  if response.status_code == 200:
20
  content = response.text
@@ -170,4 +173,4 @@ def llm_translate(to_translate: str) -> tuple[str, str]:
170
  )
171
  ai_message = model.invoke(to_translate)
172
  print("cb:", cb)
173
- return cb, ai_message.content
 
6
  from langchain_anthropic import ChatAnthropic
7
 
8
  from translator.prompt_glossary import PROMPT_WITH_GLOSSARY
9
+ from translator.project_config import get_project_config
10
 
11
 
12
+ def get_content(filepath: str, project: str = "transformers") -> str:
13
  if filepath == "":
14
  raise ValueError("No files selected for translation.")
15
 
16
+ config = get_project_config(project)
17
+ # Extract repo path from repo_url (e.g., "huggingface/transformers")
18
+ repo_path = config.repo_url.replace("https://github.com/", "")
19
+
20
+ url = f"https://raw.githubusercontent.com/{repo_path}/main/{filepath}"
21
  response = requests.get(url)
22
  if response.status_code == 200:
23
  content = response.text
 
173
  )
174
  ai_message = model.invoke(to_translate)
175
  print("cb:", cb)
176
+ return str(cb), ai_message.content