File size: 11,396 Bytes
2503583
4d193ca
 
 
2503583
 
 
 
 
 
 
 
4d193ca
 
2503583
4d193ca
 
2503583
4d193ca
 
2503583
4d193ca
2503583
4d193ca
2503583
 
4d193ca
 
2503583
4d193ca
 
 
 
 
2503583
4d193ca
 
 
2503583
4d193ca
 
 
 
 
 
2503583
4d193ca
2503583
 
 
 
 
 
 
 
 
 
 
 
 
4d193ca
 
 
 
2503583
 
4d193ca
2503583
4d193ca
 
2503583
 
4d193ca
 
 
 
 
 
 
2503583
4d193ca
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2503583
4d193ca
2503583
4d193ca
2503583
4d193ca
 
2503583
 
4d193ca
2503583
4d193ca
 
 
2503583
 
4d193ca
 
 
 
 
2503583
 
 
4d193ca
2503583
 
4d193ca
 
 
2503583
4d193ca
 
 
 
 
2503583
4d193ca
2503583
4d193ca
2503583
4d193ca
 
 
 
 
 
 
 
 
 
2503583
 
4d193ca
2503583
4d193ca
 
 
2503583
4d193ca
 
 
 
2503583
4d193ca
2503583
4d193ca
 
 
 
 
 
2503583
 
4d193ca
 
 
2503583
 
 
 
 
 
 
 
4d193ca
2503583
4d193ca
 
2503583
4d193ca
 
 
 
 
 
2503583
4d193ca
 
 
2503583
4d193ca
 
 
 
 
 
2503583
4d193ca
 
 
 
 
 
2503583
4d193ca
 
 
 
 
 
2503583
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4d193ca
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
ο»Ώ
# ./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")