prashantmatlani commited on
Commit
4d193ca
·
1 Parent(s): 2307249

history display

Browse files
Files changed (4) hide show
  1. app.py +66 -116
  2. app_02.py +262 -0
  3. storage.py +37 -27
  4. storage_00.py +123 -0
app.py CHANGED
@@ -1,75 +1,68 @@
1
- 
2
  # ./app.py
3
 
4
  """
5
- The Interface Skeleton - The code sets up the navigation panel and the multimodal chat interface
6
-
7
- 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.
8
-
9
- 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.
10
-
11
- history_list.click: This is the bridge that makes the sidebar interactive. Without this event, clicking the "Recent Conversations" wouldn't do anything.
12
-
13
  """
14
 
15
-
16
  import gradio as gr
17
  from core_logic import chat_function
18
- from storage import save_chat, load_history, get_chat_content
19
  from git_agent import manage_github_repo
20
 
21
  # Theme and css parameters not included under gr.Blocks constructor for Gradio 6
22
  with gr.Blocks() as demo:
23
- # This state keeps track of the filename for the current session
24
  chat_id_state = gr.State("")
25
-
26
- # Hidden state tracking variable to maintain files across the chat session
27
  staged_files_state = gr.State([])
28
-
29
- with gr.Row():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  # --- Left Panel: Sidebar History ---
31
  with gr.Column(scale=1, variant="secondary"):
32
  gr.Markdown("### 🛠️ Silicon Architect")
33
  new_btn = gr.Button("➕ New Chat", variant="primary")
34
 
35
- # The sidebar component
36
  history_list = gr.Dataset(
37
  components=[gr.Textbox(visible=False)],
38
  label="Recent Conversations",
39
- samples=load_history(),
40
  type="values",
41
  samples_per_page=20
42
  )
43
 
44
  # --- Center Panel: Main Core Multimodal Chat ---
45
  with gr.Column(scale=3):
46
- # type="messages" not included as it is now default/implicit in Gradio 6
47
  chatbot = gr.Chatbot(show_label=False, height=700)
48
- """
49
- chat_input = gr.MultimodalTextbox(
50
- interactive=True,
51
- placeholder="Discuss architecture or ask CoderG to produce course documentation...",
52
- show_label=False,
53
- # FIX 1: Allow users to stack multiple files before sending
54
- file_count="multiple",
55
- # FIX 2: Stop rich text clipboard objects from hijacking the paste buffer
56
- 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
57
- )
58
- """
59
- # 1. Use a standard Textbox. It treats all paste inputs strictly as raw text strings,
60
- # completely preventing the automated file-attachment generation bug.
61
  chat_input = gr.Textbox(
62
  interactive=True,
63
  placeholder="Discuss architecture, paste code blocks, or ask CoderG to produce course documentation...",
64
  show_label=False,
65
- lines=1, # <--- Crucial change: Reverts default Enter behavior back to sending
66
- max_lines=10, # <--- Keeps the box flexible so it expands when pasting large text such as code bases
67
  scale=8,
68
- submit_btn=False # <--- Removes the forced sidebar submit button, allowing 'Enter' to natively act as the submission key
69
  )
70
 
71
- # 2. Wire Up the Component Handlers - 2. Provide a distinct, clear upload node that connects to your perception agent pipeline
72
- # Triggered immediately when a user finishes choosing files in the file browser window
73
  upload_btn = gr.UploadButton(
74
  "📎 Attach Documents/Images",
75
  file_count="multiple",
@@ -77,7 +70,6 @@ with gr.Blocks() as demo:
77
  scale=2
78
  )
79
 
80
- # Visual text tracker showing exactly what files have successfully staged
81
  upload_status = gr.Markdown("")
82
 
83
  # --- Right Panel: Agentic Control Tower ---
@@ -104,47 +96,50 @@ with gr.Blocks() as demo:
104
  gr.Markdown("#### 📊 Deployment Telemetry Logs")
105
  output_log = gr.Markdown("_Awaiting local environment staging completion..._")
106
 
107
- # --- LOGIC FUNCTIONS ---
108
-
109
- # --- 1: Create a staging function to process files when uploaded ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  def handle_file_upload(uploaded_files, current_staged_files):
111
- """Processes files when selected via browser and appends to staging session state."""
112
  if not current_staged_files:
113
  current_staged_files = []
114
-
115
- # Gradio returns a list of file objects when file_count="multiple"
116
  for file_obj in uploaded_files:
117
- # Check the property format of incoming file assets from Gradio 6
118
  file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
119
  if file_path and file_path not in current_staged_files:
120
  current_staged_files.append(file_path)
121
-
122
- # Return a visual message showing how many files are safely staged
123
  status_msg = f"🟢 **{len(current_staged_files)} file(s) staged successfully and attached to next prompt.**"
124
  return current_staged_files, status_msg
125
 
126
  def bot_response(message, history, chat_id):
127
  user_content = message["text"]
128
-
129
- # 1. Pass a CLEAN copy of the historical conversation *before* appending new turns
130
- # This ensures core_logic gets a proper history trail without duplication.
131
  clean_history_snapshot = list(history)
132
-
133
- # 2. Now prepare the live local UI array for streaming feedback
134
  history.append({"role": "user", "content": user_content})
135
  history.append({"role": "assistant", "content": ""})
136
 
137
- # 3. Run the generator using your clean background state snapshot
138
  for partial_resp in chat_function(message, clean_history_snapshot):
139
  history[-1]["content"] = partial_resp
140
  yield history
141
 
142
- def handle_save(history, chat_id):
143
- # 1. Save the actual data
144
  new_id = save_chat(chat_id, history)
145
- # 2. Get the latest from hub
146
- current_list = load_history()
147
- # 3. Ensure the current one is definitely at the top
148
  if [new_id] not in current_list:
149
  current_list.insert(0, [new_id])
150
  return new_id, gr.update(samples=current_list)
@@ -155,108 +150,63 @@ with gr.Blocks() as demo:
155
  return content, chat_id
156
 
157
  def push_authorized(repo_name, commit_msg, files_list):
158
- """Triggers git_agent to build the target repository, clean up local folders, and log tasks."""
159
- # Cleanly split comma separated lists of files
160
  files = [f.strip() for f in files_list.split(",") if f.strip()]
161
-
162
  if not repo_name.strip():
163
  yield "❌ **Deployment Aborted:** Repository name cannot be empty."
164
  return
165
-
166
  yield "◌ _Connecting to GitHub REST API Engine..._"
167
  result = manage_github_repo(repo_name.strip(), commit_msg, files)
168
  yield f"{result}"
169
 
170
- # --- INTERACTION ARCHITECTURE / EVENT HANDLERS ---
 
 
 
 
 
 
 
171
 
172
- # Hook the UploadButton event listener loop to stage chosen files right away
173
  upload_btn.upload(
174
  fn=handle_file_upload,
175
  inputs=[upload_btn, staged_files_state],
176
  outputs=[staged_files_state, upload_status]
177
  )
178
 
179
- # Submission wrapper to package parameters together for your multi-format engine
180
- # When submitting, we pass BOTH the text and the hidden staged files state array!
181
  def process_submission(message_text, current_staged_files, history, chat_id):
182
  if not message_text.strip() and not current_staged_files:
183
  return history, "", current_staged_files, ""
184
-
185
- # Packaging matching the identical format of core_logic expectations
186
- payload = {
187
- "text": message_text,
188
- "files": current_staged_files
189
- }
190
-
191
- # Stream responses through bot loops and clear inputs when complete
192
  for updated_history in bot_response(payload, history, chat_id):
193
- # Continuously yield state update frames, wiping values upon initial loop entry
194
  yield updated_history, "", [], ""
195
 
196
- # 1. Bind Enter/Submit behavior for the chat input text box
197
  chat_input.submit(
198
  fn=process_submission,
199
  inputs=[chat_input, staged_files_state, chatbot, chat_id_state],
200
  outputs=[chatbot, chat_input, staged_files_state, upload_status]
201
  ).then(
202
  fn=handle_save,
203
- inputs=[chatbot, chat_id_state],
204
  outputs=[chat_id_state, history_list]
205
  )
206
 
207
- # 2. Click Sidebar Item -> Load Content
208
  history_list.click(
209
  fn=load_past_chat,
210
  inputs=[history_list],
211
  outputs=[chatbot, chat_id_state]
212
  )
213
 
214
- # 3. New Chat Button Initialization
215
  new_btn.click(
216
- fn=lambda: ([], "", [], load_history(), "", "_Awaiting local environment staging completion..._"),
217
- inputs=None,
218
  outputs=[chatbot, chat_id_state, staged_files_state, history_list, upload_status, output_log]
219
  )
220
 
221
- # 4. Bind the Control Tower Approve Action button
222
  approve_btn.click(
223
  fn=push_authorized,
224
  inputs=[target_repo, commit_txt, staged_files],
225
  outputs=[output_log]
226
  )
227
 
228
- """
229
- # 1. Submit Chat -> Stream Response -> Save -> Refresh Sidebar
230
- chat_input.submit(
231
- bot_response,
232
- [chat_input, chatbot, chat_id_state],
233
- [chatbot]
234
- ).then(
235
- handle_save,
236
- [chatbot, chat_id_state],
237
- [chat_id_state, history_list]
238
- )
239
-
240
- # 2. Click Sidebar Item -> Load Content
241
- history_list.click(
242
- load_past_chat,
243
- [history_list],
244
- [chatbot, chat_id_state]
245
- )
246
-
247
- # 3. New Chat Button Initialization
248
- new_btn.click(
249
- lambda: ([], "", load_history(), "_Awaiting local environment staging completion..._"),
250
- None,
251
- [chatbot, chat_id_state, history_list, output_log]
252
- )
253
-
254
- # 4. Bind the Control Tower Approve Action button
255
- approve_btn.click(
256
- push_authorized,
257
- [target_repo, commit_txt, staged_files],
258
- [output_log]
259
- )
260
- """
261
- # Fully consolidated theme and styling injections down into launch() parameter fields
262
  demo.launch(theme=gr.themes.Soft(), css="styles.css")
 
1
+
2
  # ./app.py
3
 
4
  """
5
+ This UI layer sets up a clean initialization screen. It uses a layout state logic gate: when the page is loaded, it shows a clean login dialog box. If a valid password is sent, the complete history is queried; if an empty string is submitted, history retrieval is bypassed entirely, and the main workspace is unlocked.
 
 
 
 
 
 
 
6
  """
7
 
 
8
  import gradio as gr
9
  from core_logic import chat_function
10
+ from storage import save_chat, load_history, get_chat_content, verify_access_token
11
  from git_agent import manage_github_repo
12
 
13
  # Theme and css parameters not included under gr.Blocks constructor for Gradio 6
14
  with gr.Blocks() as demo:
15
+ # Hidden states tracking variables
16
  chat_id_state = gr.State("")
 
 
17
  staged_files_state = gr.State([])
18
+ # Session persistent tracking string holding the password token for post-saving sync triggers
19
+ auth_token_state = gr.State("")
20
+
21
+ # ==================== LAYER 1: AUTHENTICATION GATE (Visible by default) ====================
22
+ with gr.Column(visible=True) as login_layout:
23
+ gr.Markdown("## 🔐 CoderG Enterprise Access Gate")
24
+ gr.Markdown(
25
+ "⚠️ **Workspace Notice:** You can leave the password field blank to proceed to the workspace. "
26
+ "However, if you log in without a valid security key, **no past conversations or sidebar logs will be loaded**."
27
+ )
28
+ password_input = gr.Textbox(
29
+ label="Security Access Password",
30
+ placeholder="Enter password or leave blank for sandboxed mode...",
31
+ type="password"
32
+ )
33
+ login_btn = gr.Button("Unlock Workspace Environment", variant="primary")
34
+
35
+ # ==================== LAYER 2: MAIN WORKSPACE (Hidden until validation) ====================
36
+ with gr.Row(visible=False) as main_workspace:
37
+
38
  # --- Left Panel: Sidebar History ---
39
  with gr.Column(scale=1, variant="secondary"):
40
  gr.Markdown("### 🛠️ Silicon Architect")
41
  new_btn = gr.Button("➕ New Chat", variant="primary")
42
 
43
+ # The sidebar component - starts empty and populates dynamically upon auth completion
44
  history_list = gr.Dataset(
45
  components=[gr.Textbox(visible=False)],
46
  label="Recent Conversations",
47
+ samples=[],
48
  type="values",
49
  samples_per_page=20
50
  )
51
 
52
  # --- Center Panel: Main Core Multimodal Chat ---
53
  with gr.Column(scale=3):
 
54
  chatbot = gr.Chatbot(show_label=False, height=700)
55
+
 
 
 
 
 
 
 
 
 
 
 
 
56
  chat_input = gr.Textbox(
57
  interactive=True,
58
  placeholder="Discuss architecture, paste code blocks, or ask CoderG to produce course documentation...",
59
  show_label=False,
60
+ lines=1,
61
+ max_lines=10,
62
  scale=8,
63
+ submit_btn=False
64
  )
65
 
 
 
66
  upload_btn = gr.UploadButton(
67
  "📎 Attach Documents/Images",
68
  file_count="multiple",
 
70
  scale=2
71
  )
72
 
 
73
  upload_status = gr.Markdown("")
74
 
75
  # --- Right Panel: Agentic Control Tower ---
 
96
  gr.Markdown("#### 📊 Deployment Telemetry Logs")
97
  output_log = gr.Markdown("_Awaiting local environment staging completion..._")
98
 
99
+ # --- UI ROUTING HANDLERS ---
100
+ def evaluate_login_access(password_attempt):
101
+ """Processes the state gate router logic step when the entry button is pushed."""
102
+ # 1. If it's a completely incorrect password and a master key is set, block entry completely
103
+ import storage
104
+ if storage.APP_SECRET_PASSWORD and password_attempt and not verify_access_token(password_attempt):
105
+ raise gr.Error("❌ Invalid security token entered. Access to environment denied.")
106
+
107
+ # 2. Query history using input parameters (Will yield data if correct or an empty array if blank)
108
+ loaded_samples = load_history(password_attempt)
109
+
110
+ # 3. Swap frame presentation layers, pipeline data to sidebar component, and commit key to internal state
111
+ return (
112
+ gr.update(visible=False), # Hide login layout
113
+ gr.update(visible=True), # Reveal main application workspace
114
+ gr.update(samples=loaded_samples), # Update sidebar list view dataset target
115
+ password_attempt # Persist token state internally
116
+ )
117
+
118
+ # --- CORE WORKSPACE LOGIC ---
119
  def handle_file_upload(uploaded_files, current_staged_files):
 
120
  if not current_staged_files:
121
  current_staged_files = []
 
 
122
  for file_obj in uploaded_files:
 
123
  file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
124
  if file_path and file_path not in current_staged_files:
125
  current_staged_files.append(file_path)
 
 
126
  status_msg = f"🟢 **{len(current_staged_files)} file(s) staged successfully and attached to next prompt.**"
127
  return current_staged_files, status_msg
128
 
129
  def bot_response(message, history, chat_id):
130
  user_content = message["text"]
 
 
 
131
  clean_history_snapshot = list(history)
 
 
132
  history.append({"role": "user", "content": user_content})
133
  history.append({"role": "assistant", "content": ""})
134
 
 
135
  for partial_resp in chat_function(message, clean_history_snapshot):
136
  history[-1]["content"] = partial_resp
137
  yield history
138
 
139
+ def handle_save(history, chat_id, saved_auth_token):
140
+ # Pass the verified password token to authorize sync execution workflows
141
  new_id = save_chat(chat_id, history)
142
+ current_list = load_history(saved_auth_token)
 
 
143
  if [new_id] not in current_list:
144
  current_list.insert(0, [new_id])
145
  return new_id, gr.update(samples=current_list)
 
150
  return content, chat_id
151
 
152
  def push_authorized(repo_name, commit_msg, files_list):
 
 
153
  files = [f.strip() for f in files_list.split(",") if f.strip()]
 
154
  if not repo_name.strip():
155
  yield "❌ **Deployment Aborted:** Repository name cannot be empty."
156
  return
 
157
  yield "◌ _Connecting to GitHub REST API Engine..._"
158
  result = manage_github_repo(repo_name.strip(), commit_msg, files)
159
  yield f"{result}"
160
 
161
+ # --- BIND EVENT LISTENER LIFECYCLES ---
162
+
163
+ # Auth trigger layer
164
+ login_btn.click(
165
+ fn=evaluate_login_access,
166
+ inputs=[password_input],
167
+ outputs=[login_layout, main_workspace, history_list, auth_token_state]
168
+ )
169
 
 
170
  upload_btn.upload(
171
  fn=handle_file_upload,
172
  inputs=[upload_btn, staged_files_state],
173
  outputs=[staged_files_state, upload_status]
174
  )
175
 
 
 
176
  def process_submission(message_text, current_staged_files, history, chat_id):
177
  if not message_text.strip() and not current_staged_files:
178
  return history, "", current_staged_files, ""
179
+ payload = {"text": message_text, "files": current_staged_files}
 
 
 
 
 
 
 
180
  for updated_history in bot_response(payload, history, chat_id):
 
181
  yield updated_history, "", [], ""
182
 
 
183
  chat_input.submit(
184
  fn=process_submission,
185
  inputs=[chat_input, staged_files_state, chatbot, chat_id_state],
186
  outputs=[chatbot, chat_input, staged_files_state, upload_status]
187
  ).then(
188
  fn=handle_save,
189
+ inputs=[chatbot, chat_id_state, auth_token_state], # Pulls preserved token state configuration
190
  outputs=[chat_id_state, history_list]
191
  )
192
 
 
193
  history_list.click(
194
  fn=load_past_chat,
195
  inputs=[history_list],
196
  outputs=[chatbot, chat_id_state]
197
  )
198
 
 
199
  new_btn.click(
200
+ fn=lambda token: ([], "", [], load_history(token), "", "_Awaiting local environment staging completion..._"),
201
+ inputs=[auth_token_state],
202
  outputs=[chatbot, chat_id_state, staged_files_state, history_list, upload_status, output_log]
203
  )
204
 
 
205
  approve_btn.click(
206
  fn=push_authorized,
207
  inputs=[target_repo, commit_txt, staged_files],
208
  outputs=[output_log]
209
  )
210
 
211
+ # Launch UI workspace session
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  demo.launch(theme=gr.themes.Soft(), css="styles.css")
app_02.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 
2
+ # ./app.py
3
+
4
+ """
5
+ The Interface Skeleton - The code sets up the navigation panel and the multimodal chat interface
6
+
7
+ 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.
8
+
9
+ 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.
10
+
11
+ history_list.click: This is the bridge that makes the sidebar interactive. Without this event, clicking the "Recent Conversations" wouldn't do anything.
12
+
13
+ """
14
+
15
+
16
+ import gradio as gr
17
+ from core_logic import chat_function
18
+ from storage import save_chat, load_history, get_chat_content
19
+ from git_agent import manage_github_repo
20
+
21
+ # Theme and css parameters not included under gr.Blocks constructor for Gradio 6
22
+ with gr.Blocks() as demo:
23
+ # This state keeps track of the filename for the current session
24
+ chat_id_state = gr.State("")
25
+
26
+ # Hidden state tracking variable to maintain files across the chat session
27
+ staged_files_state = gr.State([])
28
+
29
+ with gr.Row():
30
+ # --- Left Panel: Sidebar History ---
31
+ with gr.Column(scale=1, variant="secondary"):
32
+ gr.Markdown("### 🛠️ Silicon Architect")
33
+ new_btn = gr.Button("➕ New Chat", variant="primary")
34
+
35
+ # The sidebar component
36
+ history_list = gr.Dataset(
37
+ components=[gr.Textbox(visible=False)],
38
+ label="Recent Conversations",
39
+ samples=load_history(),
40
+ type="values",
41
+ samples_per_page=20
42
+ )
43
+
44
+ # --- Center Panel: Main Core Multimodal Chat ---
45
+ with gr.Column(scale=3):
46
+ # type="messages" not included as it is now default/implicit in Gradio 6
47
+ chatbot = gr.Chatbot(show_label=False, height=700)
48
+ """
49
+ chat_input = gr.MultimodalTextbox(
50
+ interactive=True,
51
+ placeholder="Discuss architecture or ask CoderG to produce course documentation...",
52
+ show_label=False,
53
+ # FIX 1: Allow users to stack multiple files before sending
54
+ file_count="multiple",
55
+ # FIX 2: Stop rich text clipboard objects from hijacking the paste buffer
56
+ 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
57
+ )
58
+ """
59
+ # 1. Use a standard Textbox. It treats all paste inputs strictly as raw text strings,
60
+ # completely preventing the automated file-attachment generation bug.
61
+ chat_input = gr.Textbox(
62
+ interactive=True,
63
+ placeholder="Discuss architecture, paste code blocks, or ask CoderG to produce course documentation...",
64
+ show_label=False,
65
+ lines=1, # <--- Crucial change: Reverts default Enter behavior back to sending
66
+ max_lines=10, # <--- Keeps the box flexible so it expands when pasting large text such as code bases
67
+ scale=8,
68
+ submit_btn=False # <--- Removes the forced sidebar submit button, allowing 'Enter' to natively act as the submission key
69
+ )
70
+
71
+ # 2. Wire Up the Component Handlers - 2. Provide a distinct, clear upload node that connects to your perception agent pipeline
72
+ # Triggered immediately when a user finishes choosing files in the file browser window
73
+ upload_btn = gr.UploadButton(
74
+ "📎 Attach Documents/Images",
75
+ file_count="multiple",
76
+ file_types=[".png", ".jpg", ".jpeg", ".bmp", ".pdf", ".xlsx", ".xls", ".docx", ".md", ".py", ".html", ".cs", ".js", ".json", ".csv", ".zip", ".tar.gz", ".log", ".txt"],
77
+ scale=2
78
+ )
79
+
80
+ # Visual text tracker showing exactly what files have successfully staged
81
+ upload_status = gr.Markdown("")
82
+
83
+ # --- Right Panel: Agentic Control Tower ---
84
+ with gr.Column(scale=1, variant="secondary"):
85
+ gr.Markdown("### 🚀 CoderG Authorization Core")
86
+ gr.Markdown("_Authorize code outputs to be compiled into dedicated remote repositories._")
87
+
88
+ target_repo = gr.Textbox(
89
+ label="Target Repository Name",
90
+ placeholder="e.g., advanced-python-course",
91
+ value="dynamic-course-repo"
92
+ )
93
+ commit_txt = gr.Textbox(
94
+ label="Commit Message",
95
+ value="Automated generation via CoderG Agent"
96
+ )
97
+ staged_files = gr.Textbox(
98
+ label="Staged Files (Comma-separated)",
99
+ value="COURSE_README.md"
100
+ )
101
+
102
+ approve_btn = gr.Button("Approve & Push to GitHub", variant="primary")
103
+
104
+ gr.Markdown("#### 📊 Deployment Telemetry Logs")
105
+ output_log = gr.Markdown("_Awaiting local environment staging completion..._")
106
+
107
+ # --- LOGIC FUNCTIONS ---
108
+
109
+ # --- 1: Create a staging function to process files when uploaded ---
110
+ def handle_file_upload(uploaded_files, current_staged_files):
111
+ """Processes files when selected via browser and appends to staging session state."""
112
+ if not current_staged_files:
113
+ current_staged_files = []
114
+
115
+ # Gradio returns a list of file objects when file_count="multiple"
116
+ for file_obj in uploaded_files:
117
+ # Check the property format of incoming file assets from Gradio 6
118
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
119
+ if file_path and file_path not in current_staged_files:
120
+ current_staged_files.append(file_path)
121
+
122
+ # Return a visual message showing how many files are safely staged
123
+ status_msg = f"🟢 **{len(current_staged_files)} file(s) staged successfully and attached to next prompt.**"
124
+ return current_staged_files, status_msg
125
+
126
+ def bot_response(message, history, chat_id):
127
+ user_content = message["text"]
128
+
129
+ # 1. Pass a CLEAN copy of the historical conversation *before* appending new turns
130
+ # This ensures core_logic gets a proper history trail without duplication.
131
+ clean_history_snapshot = list(history)
132
+
133
+ # 2. Now prepare the live local UI array for streaming feedback
134
+ history.append({"role": "user", "content": user_content})
135
+ history.append({"role": "assistant", "content": ""})
136
+
137
+ # 3. Run the generator using your clean background state snapshot
138
+ for partial_resp in chat_function(message, clean_history_snapshot):
139
+ history[-1]["content"] = partial_resp
140
+ yield history
141
+
142
+ def handle_save(history, chat_id):
143
+ # 1. Save the actual data
144
+ new_id = save_chat(chat_id, history)
145
+ # 2. Get the latest from hub
146
+ current_list = load_history()
147
+ # 3. Ensure the current one is definitely at the top
148
+ if [new_id] not in current_list:
149
+ current_list.insert(0, [new_id])
150
+ return new_id, gr.update(samples=current_list)
151
+
152
+ def load_past_chat(selected_list):
153
+ chat_id = selected_list[0]
154
+ content = get_chat_content(chat_id)
155
+ return content, chat_id
156
+
157
+ def push_authorized(repo_name, commit_msg, files_list):
158
+ """Triggers git_agent to build the target repository, clean up local folders, and log tasks."""
159
+ # Cleanly split comma separated lists of files
160
+ files = [f.strip() for f in files_list.split(",") if f.strip()]
161
+
162
+ if not repo_name.strip():
163
+ yield "❌ **Deployment Aborted:** Repository name cannot be empty."
164
+ return
165
+
166
+ yield "◌ _Connecting to GitHub REST API Engine..._"
167
+ result = manage_github_repo(repo_name.strip(), commit_msg, files)
168
+ yield f"{result}"
169
+
170
+ # --- INTERACTION ARCHITECTURE / EVENT HANDLERS ---
171
+
172
+ # Hook the UploadButton event listener loop to stage chosen files right away
173
+ upload_btn.upload(
174
+ fn=handle_file_upload,
175
+ inputs=[upload_btn, staged_files_state],
176
+ outputs=[staged_files_state, upload_status]
177
+ )
178
+
179
+ # Submission wrapper to package parameters together for your multi-format engine
180
+ # When submitting, we pass BOTH the text and the hidden staged files state array!
181
+ def process_submission(message_text, current_staged_files, history, chat_id):
182
+ if not message_text.strip() and not current_staged_files:
183
+ return history, "", current_staged_files, ""
184
+
185
+ # Packaging matching the identical format of core_logic expectations
186
+ payload = {
187
+ "text": message_text,
188
+ "files": current_staged_files
189
+ }
190
+
191
+ # Stream responses through bot loops and clear inputs when complete
192
+ for updated_history in bot_response(payload, history, chat_id):
193
+ # Continuously yield state update frames, wiping values upon initial loop entry
194
+ yield updated_history, "", [], ""
195
+
196
+ # 1. Bind Enter/Submit behavior for the chat input text box
197
+ chat_input.submit(
198
+ fn=process_submission,
199
+ inputs=[chat_input, staged_files_state, chatbot, chat_id_state],
200
+ outputs=[chatbot, chat_input, staged_files_state, upload_status]
201
+ ).then(
202
+ fn=handle_save,
203
+ inputs=[chatbot, chat_id_state],
204
+ outputs=[chat_id_state, history_list]
205
+ )
206
+
207
+ # 2. Click Sidebar Item -> Load Content
208
+ history_list.click(
209
+ fn=load_past_chat,
210
+ inputs=[history_list],
211
+ outputs=[chatbot, chat_id_state]
212
+ )
213
+
214
+ # 3. New Chat Button Initialization
215
+ new_btn.click(
216
+ fn=lambda: ([], "", [], load_history(), "", "_Awaiting local environment staging completion..._"),
217
+ inputs=None,
218
+ outputs=[chatbot, chat_id_state, staged_files_state, history_list, upload_status, output_log]
219
+ )
220
+
221
+ # 4. Bind the Control Tower Approve Action button
222
+ approve_btn.click(
223
+ fn=push_authorized,
224
+ inputs=[target_repo, commit_txt, staged_files],
225
+ outputs=[output_log]
226
+ )
227
+
228
+ """
229
+ # 1. Submit Chat -> Stream Response -> Save -> Refresh Sidebar
230
+ chat_input.submit(
231
+ bot_response,
232
+ [chat_input, chatbot, chat_id_state],
233
+ [chatbot]
234
+ ).then(
235
+ handle_save,
236
+ [chatbot, chat_id_state],
237
+ [chat_id_state, history_list]
238
+ )
239
+
240
+ # 2. Click Sidebar Item -> Load Content
241
+ history_list.click(
242
+ load_past_chat,
243
+ [history_list],
244
+ [chatbot, chat_id_state]
245
+ )
246
+
247
+ # 3. New Chat Button Initialization
248
+ new_btn.click(
249
+ lambda: ([], "", load_history(), "_Awaiting local environment staging completion..._"),
250
+ None,
251
+ [chatbot, chat_id_state, history_list, output_log]
252
+ )
253
+
254
+ # 4. Bind the Control Tower Approve Action button
255
+ approve_btn.click(
256
+ push_authorized,
257
+ [target_repo, commit_txt, staged_files],
258
+ [output_log]
259
+ )
260
+ """
261
+ # Fully consolidated theme and styling injections down into launch() parameter fields
262
+ demo.launch(theme=gr.themes.Soft(), css="styles.css")
storage.py CHANGED
@@ -14,9 +14,20 @@ from huggingface_hub import HfApi, hf_hub_download
14
  REPO_ID = "prashantmatlani/chathistorycoderg"
15
  HISTORY_DIR = "./chathistory"
16
 
 
 
 
17
  # Initialize the API with your token
18
  api = HfApi(token=os.getenv("HF_TOKEN"))
19
 
 
 
 
 
 
 
 
 
20
  def save_chat(chat_id, history):
21
  """Saves chat to local subdirectory and syncs to Hugging Face Dataset."""
22
  if not os.path.exists(HISTORY_DIR):
@@ -46,55 +57,54 @@ def save_chat(chat_id, history):
46
 
47
  return chat_id
48
 
49
- """
50
- def load_history():
51
- # Retrieves list of chat IDs from the Hub to populate the sidebar.
52
- try:
53
- # We pull the list from the Hub so the sidebar reflects all saved sessions
54
- files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
55
- chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
56
- # IMPORTANT: Sorted by newest first; return as list of lists for Gradio Dataset component
57
- return [[f] for f in sorted(chat_files, reverse=True)]
58
- except:
 
 
 
59
  return []
60
- """
61
 
62
- def load_history():
63
- """Retrieves list of chat IDs from the Hub with a fail-safe chronological sorting fallback."""
64
  try:
65
- # 1. Attempt to get rich metadata for chronological sorting
66
  repo_info = api.repo_info(repo_id=REPO_ID, repo_type="dataset", files_metadata=True)
67
 
68
  chat_files_meta = []
69
  if repo_info and hasattr(repo_info, 'siblings'):
70
  for f in repo_info.siblings:
71
- # Safe startswith check
72
- if f.rfind('chats/') == 0:
73
- filename_idx = f.rfind('/') + 1
74
- chat_id = f[filename_idx:].replace(".json", "")
75
-
76
- # Safely extract last_modified timestamp if it exists
77
  last_modified = getattr(f, 'last_modified', None)
78
  chat_files_meta.append((chat_id, last_modified))
79
 
80
- # Sort by timestamp if available; otherwise fall back to string name
81
  chat_files_meta.sort(key=lambda x: x[1] if x[1] is not None else x[0], reverse=True)
82
  return [[item[0]] for item in chat_files_meta]
83
 
84
  except Exception as metadata_error:
85
- # Log the error so it's visible in your HF Space logs instead of swallowing it
86
- print(f"[!] Metadata fetch failed, switching to fallback string sort: {metadata_error}")
87
 
88
- # 2. Fallback Layer: If metadata calls fail, immediately run your original working string method
89
  try:
90
- files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
 
91
  chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
92
  return [[f] for f in sorted(chat_files, reverse=True)]
93
  except Exception as fallback_error:
94
- print(f"[!!] Total storage access failure: {fallback_error}")
95
  return []
96
 
97
-
98
  def get_chat_content(chat_id):
99
  """Loads a specific chat's content from the Hub or local cache."""
100
  filename = f"chats/{chat_id}.json"
 
14
  REPO_ID = "prashantmatlani/chathistorycoderg"
15
  HISTORY_DIR = "./chathistory"
16
 
17
+ # Pull the master password secret key from the Hugging Face Space Environment Variables
18
+ APP_SECRET_PASSWORD = os.getenv("APP_PASSWORD", "")
19
+
20
  # Initialize the API with your token
21
  api = HfApi(token=os.getenv("HF_TOKEN"))
22
 
23
+ def verify_access_token(user_password_input):
24
+ """
25
+ Validates the user's password input against the Space environment secret.
26
+ Accepts blank input gracefully if no environment secret variable is configured.
27
+ """
28
+ target_password = APP_SECRET_PASSWORD if APP_SECRET_PASSWORD is not None else ""
29
+ return user_password_input == target_password
30
+
31
  def save_chat(chat_id, history):
32
  """Saves chat to local subdirectory and syncs to Hugging Face Dataset."""
33
  if not os.path.exists(HISTORY_DIR):
 
57
 
58
  return chat_id
59
 
60
+ def load_history(user_password_input=""):
61
+ """
62
+ Retrieves list of chat IDs from the Hub, explicitly sorted by true modification time.
63
+ Strictly yields an empty list if a required password is left blank or incorrect.
64
+ """
65
+ # Guardrail 1: If a master password exists in the space environments and the user left it blank, block history
66
+ if APP_SECRET_PASSWORD and not user_password_input:
67
+ print("[*] Security Guardrail: Empty password submitted. Bypassing history retrieval.")
68
+ return []
69
+
70
+ # Guardrail 2: Verify the password matches explicitly before querying storage
71
+ if not verify_access_token(user_password_input):
72
+ print("[!] Security Guardrail: Invalid access credentials submitted.")
73
  return []
 
74
 
 
 
75
  try:
76
+ # 1. Fetch rich metadata for chronological sorting
77
  repo_info = api.repo_info(repo_id=REPO_ID, repo_type="dataset", files_metadata=True)
78
 
79
  chat_files_meta = []
80
  if repo_info and hasattr(repo_info, 'siblings'):
81
  for f in repo_info.siblings:
82
+ # FIX: Target f.rname string property explicitly instead of the bare object instance
83
+ file_path = getattr(f, 'rname', '')
84
+
85
+ if file_path.startswith("chats/"):
86
+ chat_id = file_path.split("/")[-1].replace(".json", "")
87
+ # Safely extract last_modified timestamp metadata if available
88
  last_modified = getattr(f, 'last_modified', None)
89
  chat_files_meta.append((chat_id, last_modified))
90
 
91
+ # Sort with newest file updates positioned dynamically at index [0]
92
  chat_files_meta.sort(key=lambda x: x[1] if x[1] is not None else x[0], reverse=True)
93
  return [[item[0]] for item in chat_files_meta]
94
 
95
  except Exception as metadata_error:
96
+ print(f"[!] Primary metadata pipeline exception: {metadata_error}. Launching clean fallback channel...")
 
97
 
98
+ # 2. Complete Independent Fallback Channel (Prevents file descriptor socket reuse errors)
99
  try:
100
+ fallback_api = HfApi(token=os.getenv("HF_TOKEN"))
101
+ files = fallback_api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
102
  chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
103
  return [[f] for f in sorted(chat_files, reverse=True)]
104
  except Exception as fallback_error:
105
+ print(f"[!!] Critical Storage Interface Breakdown: {fallback_error}")
106
  return []
107
 
 
108
  def get_chat_content(chat_id):
109
  """Loads a specific chat's content from the Hub or local cache."""
110
  filename = f"chats/{chat_id}.json"
storage_00.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # ./storage_00.py
3
+
4
+ """
5
+ Persistence Layer - Handles the "Save/Load" functionality using Hugging Face Dataset as a database
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from datetime import datetime
11
+ from huggingface_hub import HfApi, hf_hub_download
12
+
13
+ # --- CONFIGURATION ---
14
+ REPO_ID = "prashantmatlani/chathistorycoderg"
15
+ HISTORY_DIR = "./chathistory"
16
+
17
+ # Initialize the API with your token
18
+ api = HfApi(token=os.getenv("HF_TOKEN"))
19
+
20
+ def save_chat(chat_id, history):
21
+ """Saves chat to local subdirectory and syncs to Hugging Face Dataset."""
22
+ if not os.path.exists(HISTORY_DIR):
23
+ os.makedirs(HISTORY_DIR)
24
+
25
+ # Generate a unique ID if none exists (e.g., for a brand new chat)
26
+ if not chat_id:
27
+ chat_id = datetime.now().strftime("%m%d%Y_%H%M%S")
28
+
29
+ filename = f"{chat_id}.json"
30
+ local_path = os.path.join(HISTORY_DIR, filename)
31
+
32
+ # 1. Save Locally
33
+ with open(local_path, "w", encoding="utf-8") as f:
34
+ json.dump(history, f, indent=4)
35
+
36
+ # 2. Sync to Hugging Face Dataset (Master Stroke Persistence)
37
+ try:
38
+ api.upload_file(
39
+ path_or_fileobj=local_path,
40
+ path_in_repo=f"chats/{filename}",
41
+ repo_id=REPO_ID,
42
+ repo_type="dataset"
43
+ )
44
+ except Exception as e:
45
+ print(f"Cloud Sync Warning: {e}")
46
+
47
+ return chat_id
48
+
49
+ """
50
+ def load_history():
51
+ # Retrieves list of chat IDs from the Hub to populate the sidebar.
52
+ try:
53
+ # We pull the list from the Hub so the sidebar reflects all saved sessions
54
+ files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
55
+ chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
56
+ # IMPORTANT: Sorted by newest first; return as list of lists for Gradio Dataset component
57
+ return [[f] for f in sorted(chat_files, reverse=True)]
58
+ except:
59
+ return []
60
+ """
61
+
62
+ def load_history():
63
+ """Retrieves list of chat IDs from the Hub with a fail-safe chronological sorting fallback."""
64
+ try:
65
+ # 1. Attempt to get rich metadata for chronological sorting
66
+ repo_info = api.repo_info(repo_id=REPO_ID, repo_type="dataset", files_metadata=True)
67
+
68
+ chat_files_meta = []
69
+ if repo_info and hasattr(repo_info, 'siblings'):
70
+ for f in repo_info.siblings:
71
+ # Safe startswith check
72
+ if f.rfind('chats/') == 0:
73
+ filename_idx = f.rfind('/') + 1
74
+ chat_id = f[filename_idx:].replace(".json", "")
75
+
76
+ # Safely extract last_modified timestamp if it exists
77
+ last_modified = getattr(f, 'last_modified', None)
78
+ chat_files_meta.append((chat_id, last_modified))
79
+
80
+ # Sort by timestamp if available; otherwise fall back to string name
81
+ chat_files_meta.sort(key=lambda x: x[1] if x[1] is not None else x[0], reverse=True)
82
+ return [[item[0]] for item in chat_files_meta]
83
+
84
+ except Exception as metadata_error:
85
+ # Log the error so it's visible in your HF Space logs instead of swallowing it
86
+ print(f"[!] Metadata fetch failed, switching to fallback string sort: {metadata_error}")
87
+
88
+ # 2. Fallback Layer: If metadata calls fail, immediately run original working string method
89
+ try:
90
+ files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
91
+ chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
92
+ return [[f] for f in sorted(chat_files, reverse=True)]
93
+ except Exception as fallback_error:
94
+ print(f"[!!] Total storage access failure: {fallback_error}")
95
+ return []
96
+
97
+
98
+ def get_chat_content(chat_id):
99
+ """Loads a specific chat's content from the Hub or local cache."""
100
+ filename = f"chats/{chat_id}.json"
101
+ local_path = os.path.join(HISTORY_DIR, f"{chat_id}.json")
102
+
103
+ try:
104
+ # Ensure local dir exists
105
+ if not os.path.exists(HISTORY_DIR):
106
+ os.makedirs(HISTORY_DIR)
107
+
108
+ # Download from Hub to keep local state fresh
109
+ from huggingface_hub import hf_hub_download
110
+ downloaded_path = hf_hub_download(
111
+ repo_id=REPO_ID,
112
+ repo_type="dataset",
113
+ filename=filename,
114
+ token=os.getenv("HF_TOKEN")
115
+ )
116
+ with open(downloaded_path, "r", encoding="utf-8") as f:
117
+ return json.load(f)
118
+ except Exception:
119
+ # Fallback to local if Hub is unreachable
120
+ if os.path.exists(local_path):
121
+ with open(local_path, "r", encoding="utf-8") as f:
122
+ return json.load(f)
123
+ return []