prashantmatlani commited on
Commit
2503583
·
1 Parent(s): c473f37

history display

Browse files
Files changed (4) hide show
  1. app.py +78 -116
  2. app_02.py +111 -67
  3. storage.py +32 -87
  4. storage_00.py +56 -69
app.py CHANGED
@@ -1,75 +1,77 @@
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 +79,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 +105,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 +159,66 @@ 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
  # ./app.py
2
 
3
  """
4
+ This UI layer sets up a clean initialization screen. It uses a layout state logic gate:
5
+ when the page is loaded, it shows a clean login dialog box. If a valid password is sent,
6
+ the complete history is queried; if an empty string or incorrect password is submitted,
7
+ history retrieval is bypassed entirely, and the main workspace is unlocked securely without global state bleeding.
 
 
 
 
8
  """
9
 
 
10
  import gradio as gr
11
  from core_logic import chat_function
12
+ from storage import save_chat, load_history, get_chat_content, get_secret_password
13
  from git_agent import manage_github_repo
14
+ import os
15
+
16
+
17
+ print("==================================================")
18
+ print(f"DEBUG: HF_TOKEN exists? {bool(os.getenv('HF_TOKEN'))}")
19
+ print(f"DEBUG: APP_PASSWORD exists? {bool(os.getenv('APP_PASSWORD'))}")
20
+ if os.getenv('APP_PASSWORD'):
21
+ print(f"DEBUG: APP_PASSWORD length is {len(os.getenv('APP_PASSWORD'))}")
22
+ print("==================================================")
23
 
 
24
  with gr.Blocks() as demo:
 
25
  chat_id_state = gr.State("")
 
 
26
  staged_files_state = gr.State([])
27
+
28
+ # Session state to securely isolate the user's validated password string per browser tab instance
29
+ user_session_password = gr.State("")
30
+
31
+ # ==================== LAYER 1: AUTHENTICATION GATE ====================
32
+ with gr.Column(visible=True) as login_layout:
33
+ gr.Markdown("## 🔐 CoderG Enterprise Access Gate")
34
+ gr.Markdown(
35
+ "⚠️ **Workspace Notice:** You can leave the password field blank to proceed directly to the workspace. "
36
+ "However, if a master environment secret key is required and left blank, **no past conversations or sidebar logs will be loaded**."
37
+ )
38
+ password_input = gr.Textbox(
39
+ label="Security Access Password",
40
+ placeholder="Enter password or leave blank for unauthenticated mode...",
41
+ type="password"
42
+ )
43
+ login_btn = gr.Button("Unlock Workspace Environment", variant="primary")
44
+
45
+ # ==================== LAYER 2: MAIN WORKSPACE ====================
46
+ with gr.Row(visible=False) as main_workspace:
47
+
48
  # --- Left Panel: Sidebar History ---
49
  with gr.Column(scale=1, variant="secondary"):
50
  gr.Markdown("### 🛠️ Silicon Architect")
51
  new_btn = gr.Button("➕ New Chat", variant="primary")
52
 
 
53
  history_list = gr.Dataset(
54
  components=[gr.Textbox(visible=False)],
55
  label="Recent Conversations",
56
+ samples=[],
57
  type="values",
58
  samples_per_page=20
59
  )
60
 
61
  # --- Center Panel: Main Core Multimodal Chat ---
62
  with gr.Column(scale=3):
 
63
  chatbot = gr.Chatbot(show_label=False, height=700)
64
+
 
 
 
 
 
 
 
 
 
 
 
 
65
  chat_input = gr.Textbox(
66
  interactive=True,
67
  placeholder="Discuss architecture, paste code blocks, or ask CoderG to produce course documentation...",
68
  show_label=False,
69
+ lines=1,
70
+ max_lines=10,
71
  scale=8,
72
+ submit_btn=False
73
  )
74
 
 
 
75
  upload_btn = gr.UploadButton(
76
  "📎 Attach Documents/Images",
77
  file_count="multiple",
 
79
  scale=2
80
  )
81
 
 
82
  upload_status = gr.Markdown("")
83
 
84
  # --- Right Panel: Agentic Control Tower ---
 
105
  gr.Markdown("#### 📊 Deployment Telemetry Logs")
106
  output_log = gr.Markdown("_Awaiting local environment staging completion..._")
107
 
108
+ # --- UI ROUTING HANDLERS ---
109
+ def process_login_validation(password_attempt):
110
+ """Step 1: Authenticates strings and updates layout toggles and browser session state."""
111
+ target_password = get_secret_password()
112
+ clean_attempt = str(password_attempt).strip() if password_attempt is not None else ""
113
+
114
+ # Explicit Guardrail: Raise an error only if they typed an incorrect password.
115
+ if target_password and clean_attempt and clean_attempt != target_password:
116
+ raise gr.Error("❌ Invalid security token entered. Access to environment denied.")
117
+
118
+ # Returns layout visibility frames alongside the locked-in session password state string
119
+ return gr.update(visible=False), gr.update(visible=True), clean_attempt
120
+
121
+ def populate_history_component(session_password):
122
+ """Step 2: Updates dataset samples based cleanly on isolated session token values."""
123
+ # Executes your requested logic: passing session_password down into the storage mechanism
124
+ loaded_samples = load_history(user_password=session_password)
125
+ return gr.update(samples=loaded_samples)
126
 
127
+ # --- CORE WORKSPACE LOGIC ---
128
  def handle_file_upload(uploaded_files, current_staged_files):
 
129
  if not current_staged_files:
130
  current_staged_files = []
 
 
131
  for file_obj in uploaded_files:
 
132
  file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
133
  if file_path and file_path not in current_staged_files:
134
  current_staged_files.append(file_path)
 
 
135
  status_msg = f"🟢 **{len(current_staged_files)} file(s) staged successfully and attached to next prompt.**"
136
  return current_staged_files, status_msg
137
 
138
  def bot_response(message, history, chat_id):
139
  user_content = message["text"]
 
 
 
140
  clean_history_snapshot = list(history)
 
 
141
  history.append({"role": "user", "content": user_content})
142
  history.append({"role": "assistant", "content": ""})
143
 
 
144
  for partial_resp in chat_function(message, clean_history_snapshot):
145
  history[-1]["content"] = partial_resp
146
  yield history
147
 
148
+ def handle_save(history, chat_id, session_password):
 
149
  new_id = save_chat(chat_id, history)
150
+ # Keeps sidebar refreshes tied strictly to the current session token authentication context
151
+ current_list = load_history(user_password=session_password)
 
152
  if [new_id] not in current_list:
153
  current_list.insert(0, [new_id])
154
  return new_id, gr.update(samples=current_list)
 
159
  return content, chat_id
160
 
161
  def push_authorized(repo_name, commit_msg, files_list):
 
 
162
  files = [f.strip() for f in files_list.split(",") if f.strip()]
 
163
  if not repo_name.strip():
164
  yield "❌ **Deployment Aborted:** Repository name cannot be empty."
165
  return
 
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
+ # ==================== BIND EVENT LISTENER LIFECYCLES ====================
171
+
172
+ # Decoupled sequence: Click validates credentials and captures token -> THEN queries data array conditionally
173
+ login_btn.click(
174
+ fn=process_login_validation,
175
+ inputs=[password_input],
176
+ outputs=[login_layout, main_workspace, user_session_password]
177
+ ).then(
178
+ fn=populate_history_component,
179
+ inputs=[user_session_password],
180
+ outputs=[history_list]
181
+ )
182
 
 
183
  upload_btn.upload(
184
  fn=handle_file_upload,
185
  inputs=[upload_btn, staged_files_state],
186
  outputs=[staged_files_state, upload_status]
187
  )
188
 
 
 
189
  def process_submission(message_text, current_staged_files, history, chat_id):
190
  if not message_text.strip() and not current_staged_files:
191
  return history, "", current_staged_files, ""
192
+ payload = {"text": message_text, "files": current_staged_files}
 
 
 
 
 
 
 
193
  for updated_history in bot_response(payload, history, chat_id):
 
194
  yield updated_history, "", [], ""
195
 
 
196
  chat_input.submit(
197
  fn=process_submission,
198
  inputs=[chat_input, staged_files_state, chatbot, chat_id_state],
199
  outputs=[chatbot, chat_input, staged_files_state, upload_status]
200
  ).then(
201
  fn=handle_save,
202
+ inputs=[chatbot, chat_id_state, user_session_password],
203
  outputs=[chat_id_state, history_list]
204
  )
205
 
 
206
  history_list.click(
207
  fn=load_past_chat,
208
  inputs=[history_list],
209
  outputs=[chatbot, chat_id_state]
210
  )
211
 
 
212
  new_btn.click(
213
+ fn=lambda session_pass: ([], "", [], load_history(user_password=session_pass), "", "_Awaiting local environment staging completion..._"),
214
+ inputs=[user_session_password],
215
  outputs=[chatbot, chat_id_state, staged_files_state, history_list, upload_status, output_log]
216
  )
217
 
 
218
  approve_btn.click(
219
  fn=push_authorized,
220
  inputs=[target_repo, commit_txt, staged_files],
221
  outputs=[output_log]
222
  )
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  demo.launch(theme=gr.themes.Soft(), css="styles.css")
app_02.py CHANGED
@@ -1,72 +1,75 @@
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_and_unlock, get_secret_password
11
  from git_agent import manage_github_repo
12
- import os
13
-
14
-
15
- print("==================================================")
16
- print(f"DEBUG: HF_TOKEN exists? {bool(os.getenv('HF_TOKEN'))}")
17
- print(f"DEBUG: APP_PASSWORD exists? {bool(os.getenv('APP_PASSWORD'))}")
18
- if os.getenv('APP_PASSWORD'):
19
- print(f"DEBUG: APP_PASSWORD length is {len(os.getenv('APP_PASSWORD'))}")
20
- print("==================================================")
21
 
 
22
  with gr.Blocks() as demo:
 
23
  chat_id_state = gr.State("")
 
 
24
  staged_files_state = gr.State([])
25
 
26
- # ==================== LAYER 1: AUTHENTICATION GATE ====================
27
- with gr.Column(visible=True) as login_layout:
28
- gr.Markdown("## 🔐 CoderG Enterprise Access Gate")
29
- gr.Markdown(
30
- "⚠️ **Workspace Notice:** You can leave the password field blank to proceed directly to the workspace. "
31
- "However, if a master environment secret key is required and left blank, **no past conversations or sidebar logs will be loaded**."
32
- )
33
- password_input = gr.Textbox(
34
- label="Security Access Password",
35
- placeholder="Enter password or leave blank for unauthenticated mode...",
36
- type="password"
37
- )
38
- login_btn = gr.Button("Unlock Workspace Environment", variant="primary")
39
-
40
- # ==================== LAYER 2: MAIN WORKSPACE ====================
41
- with gr.Row(visible=False) as main_workspace:
42
-
43
  # --- Left Panel: Sidebar History ---
44
  with gr.Column(scale=1, variant="secondary"):
45
  gr.Markdown("### 🛠️ Silicon Architect")
46
  new_btn = gr.Button("➕ New Chat", variant="primary")
47
 
 
48
  history_list = gr.Dataset(
49
  components=[gr.Textbox(visible=False)],
50
  label="Recent Conversations",
51
- samples=[],
52
  type="values",
53
  samples_per_page=20
54
  )
55
 
56
  # --- Center Panel: Main Core Multimodal Chat ---
57
  with gr.Column(scale=3):
 
58
  chatbot = gr.Chatbot(show_label=False, height=700)
59
-
 
 
 
 
 
 
 
 
 
 
 
 
60
  chat_input = gr.Textbox(
61
  interactive=True,
62
  placeholder="Discuss architecture, paste code blocks, or ask CoderG to produce course documentation...",
63
  show_label=False,
64
- lines=1,
65
- max_lines=10,
66
  scale=8,
67
- submit_btn=False
68
  )
69
 
 
 
70
  upload_btn = gr.UploadButton(
71
  "📎 Attach Documents/Images",
72
  file_count="multiple",
@@ -74,6 +77,7 @@ with gr.Blocks() as demo:
74
  scale=2
75
  )
76
 
 
77
  upload_status = gr.Markdown("")
78
 
79
  # --- Right Panel: Agentic Control Tower ---
@@ -100,49 +104,47 @@ with gr.Blocks() as demo:
100
  gr.Markdown("#### 📊 Deployment Telemetry Logs")
101
  output_log = gr.Markdown("_Awaiting local environment staging completion..._")
102
 
103
- # --- UI ROUTING HANDLERS ---
104
- def process_login_validation(password_attempt):
105
- """Step 1: Authenticates input strings and toggles structural layout visibility gates."""
106
- target_password = get_secret_password()
107
- clean_attempt = str(password_attempt).strip() if password_attempt is not None else ""
108
-
109
- if target_password and clean_attempt and clean_attempt != target_password:
110
- raise gr.Error("❌ Invalid security token entered. Access to environment denied.")
111
-
112
- # Unlocks storage session variables seamlessly in backend memory
113
- verify_and_unlock(clean_attempt)
114
-
115
- return gr.update(visible=False), gr.update(visible=True)
116
-
117
- def populate_history_component():
118
- """Step 2: Safely updates dataset metrics once components are fully visible on screen."""
119
- loaded_samples = load_history()
120
- return gr.update(samples=loaded_samples)
121
 
122
- # --- CORE WORKSPACE LOGIC ---
123
  def handle_file_upload(uploaded_files, current_staged_files):
 
124
  if not current_staged_files:
125
  current_staged_files = []
 
 
126
  for file_obj in uploaded_files:
 
127
  file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
128
  if file_path and file_path not in current_staged_files:
129
  current_staged_files.append(file_path)
 
 
130
  status_msg = f"🟢 **{len(current_staged_files)} file(s) staged successfully and attached to next prompt.**"
131
  return current_staged_files, status_msg
132
 
133
  def bot_response(message, history, chat_id):
134
  user_content = message["text"]
 
 
 
135
  clean_history_snapshot = list(history)
 
 
136
  history.append({"role": "user", "content": user_content})
137
  history.append({"role": "assistant", "content": ""})
138
 
 
139
  for partial_resp in chat_function(message, clean_history_snapshot):
140
  history[-1]["content"] = partial_resp
141
  yield history
142
 
143
  def handle_save(history, chat_id):
 
144
  new_id = save_chat(chat_id, history)
 
145
  current_list = load_history()
 
146
  if [new_id] not in current_list:
147
  current_list.insert(0, [new_id])
148
  return new_id, gr.update(samples=current_list)
@@ -153,66 +155,108 @@ with gr.Blocks() as demo:
153
  return content, chat_id
154
 
155
  def push_authorized(repo_name, commit_msg, files_list):
 
 
156
  files = [f.strip() for f in files_list.split(",") if f.strip()]
 
157
  if not repo_name.strip():
158
  yield "❌ **Deployment Aborted:** Repository name cannot be empty."
159
  return
 
160
  yield "◌ _Connecting to GitHub REST API Engine..._"
161
  result = manage_github_repo(repo_name.strip(), commit_msg, files)
162
  yield f"{result}"
163
 
164
- # ==================== BIND EVENT LISTENER LIFECYCLES ====================
165
-
166
- # Decoupled sequence: Click validates & renders frames -> THEN queries data array
167
- login_btn.click(
168
- fn=process_login_validation,
169
- inputs=[password_input],
170
- outputs=[login_layout, main_workspace]
171
- ).then(
172
- fn=populate_history_component,
173
- inputs=None,
174
- outputs=[history_list]
175
- )
176
 
 
177
  upload_btn.upload(
178
  fn=handle_file_upload,
179
  inputs=[upload_btn, staged_files_state],
180
  outputs=[staged_files_state, upload_status]
181
  )
182
 
 
 
183
  def process_submission(message_text, current_staged_files, history, chat_id):
184
  if not message_text.strip() and not current_staged_files:
185
  return history, "", current_staged_files, ""
186
- payload = {"text": message_text, "files": current_staged_files}
 
 
 
 
 
 
 
187
  for updated_history in bot_response(payload, history, chat_id):
 
188
  yield updated_history, "", [], ""
189
 
 
190
  chat_input.submit(
191
  fn=process_submission,
192
  inputs=[chat_input, staged_files_state, chatbot, chat_id_state],
193
  outputs=[chatbot, chat_input, staged_files_state, upload_status]
194
  ).then(
195
  fn=handle_save,
196
- inputs=[chatbot, chat_id_state],
197
  outputs=[chat_id_state, history_list]
198
  )
199
 
 
200
  history_list.click(
201
  fn=load_past_chat,
202
  inputs=[history_list],
203
  outputs=[chatbot, chat_id_state]
204
  )
205
 
 
206
  new_btn.click(
207
  fn=lambda: ([], "", [], load_history(), "", "_Awaiting local environment staging completion..._"),
208
  inputs=None,
209
  outputs=[chatbot, chat_id_state, staged_files_state, history_list, upload_status, output_log]
210
  )
211
 
 
212
  approve_btn.click(
213
  fn=push_authorized,
214
  inputs=[target_repo, commit_txt, staged_files],
215
  outputs=[output_log]
216
  )
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  demo.launch(theme=gr.themes.Soft(), css="styles.css")
 
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
  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
  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
  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
@@ -1,5 +1,5 @@
1
 
2
- # ./storage_00.py
3
 
4
  """
5
  Persistence Layer - Handles the "Save/Load" functionality using Hugging Face Dataset as a database
@@ -10,114 +10,59 @@ 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 []
 
1
 
2
+ # ./storage.py
3
 
4
  """
5
  Persistence Layer - Handles the "Save/Load" functionality using Hugging Face Dataset as a database
 
10
  from datetime import datetime
11
  from huggingface_hub import HfApi, hf_hub_download
12
 
 
13
  REPO_ID = "prashantmatlani/chathistorycoderg"
14
  HISTORY_DIR = "./chathistory"
15
 
 
16
  api = HfApi(token=os.getenv("HF_TOKEN"))
17
 
18
+ def get_secret_password():
19
+ """Dynamically reads the environment secret to prevent cached empty variables at startup."""
20
+ val = os.getenv("APP_PASSWORD")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ print(f"\nlen val: {len(val)}\n")
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ return str(val).strip() if val is not None else ""
25
+
26
+ def load_history(user_password=""):
27
+ """Retrieves list of chat IDs from the Hub ONLY if the password matches the secret."""
28
+ target_password = get_secret_password()
29
+ clean_input = str(user_password).strip()
30
+
31
+ # ====================================================================
32
+ # 🔐 YOUR NEW IF / ELSE SECURITY CONFIGURATION
33
+ # ====================================================================
34
+
35
+ # Condition 1: If the password field is left blank OR if it doesn't match the secret...
36
+ if not clean_input or clean_input != target_password:
37
+ print("[*] Security Guardrail: Invalid or empty password submitted. Bypassing history retrieval.")
38
+ return [] # --> Then: No history loading
39
+
40
+ # Condition 2: Otherwise (it matches perfectly), proceed to load history
41
+ print("[+] Security Pass: Credentials verified. Loading environment histories...")
42
+
43
  try:
 
44
  repo_info = api.repo_info(repo_id=REPO_ID, repo_type="dataset", files_metadata=True)
 
45
  chat_files_meta = []
46
  if repo_info and hasattr(repo_info, 'siblings'):
47
  for f in repo_info.siblings:
48
+ file_path = getattr(f, 'rname', '')
49
+ if file_path.startswith("chats/"):
50
+ chat_id = file_path.split("/")[-1].replace(".json", "")
 
 
 
51
  last_modified = getattr(f, 'last_modified', None)
52
  chat_files_meta.append((chat_id, last_modified))
53
 
 
54
  chat_files_meta.sort(key=lambda x: x[1] if x[1] is not None else x[0], reverse=True)
55
  return [[item[0]] for item in chat_files_meta]
 
56
  except Exception as metadata_error:
57
+ print(f"[!] Primary metadata pipeline exception: {metadata_error}")
 
58
 
 
59
  try:
60
+ fallback_api = HfApi(token=os.getenv("HF_TOKEN"))
61
+ files = fallback_api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
62
  chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
63
  return [[f] for f in sorted(chat_files, reverse=True)]
64
  except Exception as fallback_error:
65
+ print(f"[!!] Critical Storage Interface Breakdown: {fallback_error}")
66
  return []
67
 
68
+ # Keep your existing save_chat and get_chat_content below unchanged...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
storage_00.py CHANGED
@@ -1,5 +1,5 @@
1
 
2
- # ./storage.py
3
 
4
  """
5
  Persistence Layer - Handles the "Save/Load" functionality using Hugging Face Dataset as a database
@@ -14,103 +14,86 @@ from huggingface_hub import HfApi, hf_hub_download
14
  REPO_ID = "prashantmatlani/chathistorycoderg"
15
  HISTORY_DIR = "./chathistory"
16
 
17
- # Internal Session Authentication Toggle
18
- _SESSION_UNLOCKED = False
19
-
20
  # Initialize the API with your token
21
  api = HfApi(token=os.getenv("HF_TOKEN"))
22
 
23
- def get_secret_password():
24
- """Dynamically reads the environment secret to prevent cached empty variables at startup."""
25
- val = os.getenv("APP_PASSWORD")
26
-
27
- print(f"\nlen val: {len(val)}\n")
28
-
29
- return str(val).strip() if val is not None else ""
30
-
31
- def verify_and_unlock(user_password_input):
32
- """Validates input credentials and flips the internal tracking state variable."""
33
- global _SESSION_UNLOCKED
34
- target_password = get_secret_password()
35
- clean_input = str(user_password_input).strip()
36
 
37
- print(f"len target_password: {len(target_password)}, len clean_input: {len(clean_input)}")
38
-
39
- # If no master environment password exists, unlock access automatically
40
- if not target_password:
41
- _SESSION_UNLOCKED = True
42
- return True
43
-
44
- if clean_input == target_password:
45
- _SESSION_UNLOCKED = True
46
- return True
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- return False
49
 
 
50
  def load_history():
51
- """Retrieves list of chat IDs from the Hub, explicitly sorted by true modification time."""
52
- global _SESSION_UNLOCKED
53
-
54
- # Guardrail: If an environment password is required but the session remains locked, return empty list
55
- if get_secret_password() and not _SESSION_UNLOCKED:
56
- print("[*] Security Guardrail: Bypassing history retrieval for unauthenticated lifecycle.")
 
 
57
  return []
 
58
 
 
 
59
  try:
60
- # 1. Fetch rich metadata for chronological sorting
61
  repo_info = api.repo_info(repo_id=REPO_ID, repo_type="dataset", files_metadata=True)
62
 
63
  chat_files_meta = []
64
  if repo_info and hasattr(repo_info, 'siblings'):
65
  for f in repo_info.siblings:
66
- file_path = getattr(f, 'rname', '')
67
- if file_path.startswith("chats/"):
68
- chat_id = file_path.split("/")[-1].replace(".json", "")
 
 
 
69
  last_modified = getattr(f, 'last_modified', None)
70
  chat_files_meta.append((chat_id, last_modified))
71
 
72
- # Sort chronologically with true latest updates positioned at list index [0]
73
  chat_files_meta.sort(key=lambda x: x[1] if x[1] is not None else x[0], reverse=True)
74
  return [[item[0]] for item in chat_files_meta]
75
 
76
  except Exception as metadata_error:
77
- print(f"[!] Primary metadata pipeline exception: {metadata_error}. Launching clean fallback channel...")
 
78
 
79
- # 2. Fallback Channel
80
  try:
81
- fallback_api = HfApi(token=os.getenv("HF_TOKEN"))
82
- files = fallback_api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
83
  chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
84
  return [[f] for f in sorted(chat_files, reverse=True)]
85
  except Exception as fallback_error:
86
- print(f"[!!] Critical Storage Interface Breakdown: {fallback_error}")
87
  return []
88
 
89
- def save_chat(chat_id, history):
90
- """Saves chat to local subdirectory and syncs to Hugging Face Dataset."""
91
- if not os.path.exists(HISTORY_DIR):
92
- os.makedirs(HISTORY_DIR)
93
-
94
- if not chat_id:
95
- chat_id = datetime.now().strftime("%m%d%Y_%H%M%S")
96
-
97
- filename = f"{chat_id}.json"
98
- local_path = os.path.join(HISTORY_DIR, filename)
99
-
100
- with open(local_path, "w", encoding="utf-8") as f:
101
- json.dump(history, f, indent=4)
102
-
103
- try:
104
- api.upload_file(
105
- path_or_fileobj=local_path,
106
- path_in_repo=f"chats/{filename}",
107
- repo_id=REPO_ID,
108
- repo_type="dataset"
109
- )
110
- except Exception as e:
111
- print(f"Cloud Sync Warning: {e}")
112
-
113
- return chat_id
114
 
115
  def get_chat_content(chat_id):
116
  """Loads a specific chat's content from the Hub or local cache."""
@@ -118,9 +101,12 @@ def get_chat_content(chat_id):
118
  local_path = os.path.join(HISTORY_DIR, f"{chat_id}.json")
119
 
120
  try:
 
121
  if not os.path.exists(HISTORY_DIR):
122
  os.makedirs(HISTORY_DIR)
123
 
 
 
124
  downloaded_path = hf_hub_download(
125
  repo_id=REPO_ID,
126
  repo_type="dataset",
@@ -130,6 +116,7 @@ def get_chat_content(chat_id):
130
  with open(downloaded_path, "r", encoding="utf-8") as f:
131
  return json.load(f)
132
  except Exception:
 
133
  if os.path.exists(local_path):
134
  with open(local_path, "r", encoding="utf-8") as f:
135
  return json.load(f)
 
1
 
2
+ # ./storage_00.py
3
 
4
  """
5
  Persistence Layer - Handles the "Save/Load" functionality using Hugging Face Dataset as a database
 
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."""
 
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",
 
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)