Spaces:
Sleeping
Sleeping
| | |
| # ./app.py | |
| """ | |
| The Interface Skeleton - The code sets up the navigation panel and the multimodal chat interface | |
| The .then() chain: Previously, the save happened "in the background." Now, handle_save explicitly returns the new load_history() results to the history_list component, causing it to "re-render" with the new chat visible. | |
| The chat_id_state: By passing this back and forth, the app knows if it should update an existing file in the HF Dataset or create a new one. | |
| history_list.click: This is the bridge that makes the sidebar interactive. Without this event, clicking the "Recent Conversations" wouldn't do anything. | |
| """ | |
| import gradio as gr | |
| from core_logic import chat_function | |
| from storage import save_chat, load_history, get_chat_content | |
| from git_agent import manage_github_repo | |
| # Theme and css parameters not included under gr.Blocks constructor for Gradio 6 | |
| with gr.Blocks() as demo: | |
| # This state keeps track of the filename for the current session | |
| chat_id_state = gr.State("") | |
| # Hidden state tracking variable to maintain files across the chat session | |
| staged_files_state = gr.State([]) | |
| with gr.Row(): | |
| # --- Left Panel: Sidebar History --- | |
| with gr.Column(scale=1, variant="secondary"): | |
| gr.Markdown("### 🛠️ Silicon Architect") | |
| new_btn = gr.Button("➕ New Chat", variant="primary") | |
| # The sidebar component | |
| history_list = gr.Dataset( | |
| components=[gr.Textbox(visible=False)], | |
| label="Recent Conversations", | |
| samples=load_history(), | |
| type="values", | |
| samples_per_page=20 | |
| ) | |
| # --- Center Panel: Main Core Multimodal Chat --- | |
| with gr.Column(scale=3): | |
| # type="messages" not included as it is now default/implicit in Gradio 6 | |
| chatbot = gr.Chatbot(show_label=False, height=700) | |
| """ | |
| chat_input = gr.MultimodalTextbox( | |
| interactive=True, | |
| placeholder="Discuss architecture or ask CoderG to produce course documentation...", | |
| show_label=False, | |
| # FIX 1: Allow users to stack multiple files before sending | |
| file_count="multiple", | |
| # FIX 2: Stop rich text clipboard objects from hijacking the paste buffer | |
| file_types=[".png", ".jpg", ".jpeg", ".bmp", ".pdf", ".xlsx", ".xls", ".docx", ".md", ".py", ".html", ".cs", ".js", ".json", ".csv", ".zip", ".tar.gz", ".log", ".txt"] # Expanded file type support | |
| ) | |
| """ | |
| # 1. Use a standard Textbox. It treats all paste inputs strictly as raw text strings, | |
| # completely preventing the automated file-attachment generation bug. | |
| chat_input = gr.Textbox( | |
| interactive=True, | |
| placeholder="Discuss architecture, paste code blocks, or ask CoderG to produce course documentation...", | |
| show_label=False, | |
| lines=1, # <--- Crucial change: Reverts default Enter behavior back to sending | |
| max_lines=10, # <--- Keeps the box flexible so it expands when pasting large text such as code bases | |
| scale=8, | |
| submit_btn=False # <--- Removes the forced sidebar submit button, allowing 'Enter' to natively act as the submission key | |
| ) | |
| # 2. Wire Up the Component Handlers - 2. Provide a distinct, clear upload node that connects to your perception agent pipeline | |
| # Triggered immediately when a user finishes choosing files in the file browser window | |
| upload_btn = gr.UploadButton( | |
| "📎 Attach Documents/Images", | |
| file_count="multiple", | |
| file_types=[".png", ".jpg", ".jpeg", ".bmp", ".pdf", ".xlsx", ".xls", ".docx", ".md", ".py", ".html", ".cs", ".js", ".json", ".csv", ".zip", ".tar.gz", ".log", ".txt"], | |
| scale=2 | |
| ) | |
| # Visual text tracker showing exactly what files have successfully staged | |
| upload_status = gr.Markdown("") | |
| # --- Right Panel: Agentic Control Tower --- | |
| with gr.Column(scale=1, variant="secondary"): | |
| gr.Markdown("### 🚀 CoderG Authorization Core") | |
| gr.Markdown("_Authorize code outputs to be compiled into dedicated remote repositories._") | |
| target_repo = gr.Textbox( | |
| label="Target Repository Name", | |
| placeholder="e.g., advanced-python-course", | |
| value="dynamic-course-repo" | |
| ) | |
| commit_txt = gr.Textbox( | |
| label="Commit Message", | |
| value="Automated generation via CoderG Agent" | |
| ) | |
| staged_files = gr.Textbox( | |
| label="Staged Files (Comma-separated)", | |
| value="COURSE_README.md" | |
| ) | |
| approve_btn = gr.Button("Approve & Push to GitHub", variant="primary") | |
| gr.Markdown("#### 📊 Deployment Telemetry Logs") | |
| output_log = gr.Markdown("_Awaiting local environment staging completion..._") | |
| # --- LOGIC FUNCTIONS --- | |
| # --- 1: Create a staging function to process files when uploaded --- | |
| def handle_file_upload(uploaded_files, current_staged_files): | |
| """Processes files when selected via browser and appends to staging session state.""" | |
| if not current_staged_files: | |
| current_staged_files = [] | |
| # Gradio returns a list of file objects when file_count="multiple" | |
| for file_obj in uploaded_files: | |
| # Check the property format of incoming file assets from Gradio 6 | |
| file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj | |
| if file_path and file_path not in current_staged_files: | |
| current_staged_files.append(file_path) | |
| # Return a visual message showing how many files are safely staged | |
| status_msg = f"🟢 **{len(current_staged_files)} file(s) staged successfully and attached to next prompt.**" | |
| return current_staged_files, status_msg | |
| def bot_response(message, history, chat_id): | |
| user_content = message["text"] | |
| # 1. Pass a CLEAN copy of the historical conversation *before* appending new turns | |
| # This ensures core_logic gets a proper history trail without duplication. | |
| clean_history_snapshot = list(history) | |
| # 2. Now prepare the live local UI array for streaming feedback | |
| history.append({"role": "user", "content": user_content}) | |
| history.append({"role": "assistant", "content": ""}) | |
| # 3. Run the generator using your clean background state snapshot | |
| for partial_resp in chat_function(message, clean_history_snapshot): | |
| history[-1]["content"] = partial_resp | |
| yield history | |
| def handle_save(history, chat_id): | |
| # 1. Save the actual data | |
| new_id = save_chat(chat_id, history) | |
| # 2. Get the latest from hub | |
| current_list = load_history() | |
| # 3. Ensure the current one is definitely at the top | |
| if [new_id] not in current_list: | |
| current_list.insert(0, [new_id]) | |
| return new_id, gr.update(samples=current_list) | |
| def load_past_chat(selected_list): | |
| chat_id = selected_list[0] | |
| content = get_chat_content(chat_id) | |
| return content, chat_id | |
| def push_authorized(repo_name, commit_msg, files_list): | |
| """Triggers git_agent to build the target repository, clean up local folders, and log tasks.""" | |
| # Cleanly split comma separated lists of files | |
| files = [f.strip() for f in files_list.split(",") if f.strip()] | |
| if not repo_name.strip(): | |
| yield "❌ **Deployment Aborted:** Repository name cannot be empty." | |
| return | |
| yield "◌ _Connecting to GitHub REST API Engine..._" | |
| result = manage_github_repo(repo_name.strip(), commit_msg, files) | |
| yield f"{result}" | |
| # --- INTERACTION ARCHITECTURE / EVENT HANDLERS --- | |
| # Hook the UploadButton event listener loop to stage chosen files right away | |
| upload_btn.upload( | |
| fn=handle_file_upload, | |
| inputs=[upload_btn, staged_files_state], | |
| outputs=[staged_files_state, upload_status] | |
| ) | |
| # Submission wrapper to package parameters together for your multi-format engine | |
| # When submitting, we pass BOTH the text and the hidden staged files state array! | |
| def process_submission(message_text, current_staged_files, history, chat_id): | |
| if not message_text.strip() and not current_staged_files: | |
| return history, "", current_staged_files, "" | |
| # Packaging matching the identical format of core_logic expectations | |
| payload = { | |
| "text": message_text, | |
| "files": current_staged_files | |
| } | |
| # Stream responses through bot loops and clear inputs when complete | |
| for updated_history in bot_response(payload, history, chat_id): | |
| # Continuously yield state update frames, wiping values upon initial loop entry | |
| yield updated_history, "", [], "" | |
| # 1. Bind Enter/Submit behavior for the chat input text box | |
| chat_input.submit( | |
| fn=process_submission, | |
| inputs=[chat_input, staged_files_state, chatbot, chat_id_state], | |
| outputs=[chatbot, chat_input, staged_files_state, upload_status] | |
| ).then( | |
| fn=handle_save, | |
| inputs=[chatbot, chat_id_state], | |
| outputs=[chat_id_state, history_list] | |
| ) | |
| # 2. Click Sidebar Item -> Load Content | |
| history_list.click( | |
| fn=load_past_chat, | |
| inputs=[history_list], | |
| outputs=[chatbot, chat_id_state] | |
| ) | |
| # 3. New Chat Button Initialization | |
| new_btn.click( | |
| fn=lambda: ([], "", [], load_history(), "", "_Awaiting local environment staging completion..._"), | |
| inputs=None, | |
| outputs=[chatbot, chat_id_state, staged_files_state, history_list, upload_status, output_log] | |
| ) | |
| # 4. Bind the Control Tower Approve Action button | |
| approve_btn.click( | |
| fn=push_authorized, | |
| inputs=[target_repo, commit_txt, staged_files], | |
| outputs=[output_log] | |
| ) | |
| """ | |
| # 1. Submit Chat -> Stream Response -> Save -> Refresh Sidebar | |
| chat_input.submit( | |
| bot_response, | |
| [chat_input, chatbot, chat_id_state], | |
| [chatbot] | |
| ).then( | |
| handle_save, | |
| [chatbot, chat_id_state], | |
| [chat_id_state, history_list] | |
| ) | |
| # 2. Click Sidebar Item -> Load Content | |
| history_list.click( | |
| load_past_chat, | |
| [history_list], | |
| [chatbot, chat_id_state] | |
| ) | |
| # 3. New Chat Button Initialization | |
| new_btn.click( | |
| lambda: ([], "", load_history(), "_Awaiting local environment staging completion..._"), | |
| None, | |
| [chatbot, chat_id_state, history_list, output_log] | |
| ) | |
| # 4. Bind the Control Tower Approve Action button | |
| approve_btn.click( | |
| push_authorized, | |
| [target_repo, commit_txt, staged_files], | |
| [output_log] | |
| ) | |
| """ | |
| # Fully consolidated theme and styling injections down into launch() parameter fields | |
| demo.launch(theme=gr.themes.Soft(), css="styles.css") |