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

history display

Browse files
Files changed (4) hide show
  1. app.py +111 -63
  2. app_02.py +67 -111
  3. storage.py +57 -51
  4. storage_00.py +69 -56
app.py CHANGED
@@ -1,74 +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_access_token, get_secret_password
11
  from git_agent import manage_github_repo
12
- import os
13
-
14
- print("==================================================")
15
- print(f"DEBUG: HF_TOKEN exists? {bool(os.getenv('HF_TOKEN'))}")
16
- print(f"DEBUG: APP_PASSWORD exists? {bool(os.getenv('APP_PASSWORD'))}")
17
- if os.getenv('APP_PASSWORD'):
18
- print(f"DEBUG: APP_PASSWORD length is {len(os.getenv('APP_PASSWORD'))}")
19
- print("==================================================")
20
 
 
21
  with gr.Blocks() as demo:
 
22
  chat_id_state = gr.State("")
 
 
23
  staged_files_state = gr.State([])
24
 
25
- # ==================== AUTHENTICATION OVERLAY HEADER ====================
26
- with gr.Group(visible=True) as login_layout:
27
- gr.Markdown("## 🔐 CoderG Enterprise Access Gate")
28
- gr.Markdown(
29
- "⚠️ **Workspace Notice:** You can leave the password field blank to proceed directly to the workspace. "
30
- "However, if a master environment secret key is required and left blank, **no past conversations or sidebar logs will be loaded**."
31
- )
32
- with gr.Row():
33
- password_input = gr.Textbox(
34
- label="Security Access Password",
35
- placeholder="Enter password or leave blank for unauthenticated mode...",
36
- type="password",
37
- scale=4
38
- )
39
- login_btn = gr.Button("Unlock Workspace Environment", variant="primary", scale=1)
40
-
41
- # ==================== MAIN ENVIRONMENT INTERFACE ====================
42
- # Notice: Row stays visible immediately so components load and paint their slots cleanly on boot!
43
- with gr.Row(visible=True) as main_workspace:
44
-
45
  # --- Left Panel: Sidebar History ---
46
  with gr.Column(scale=1, variant="secondary"):
47
  gr.Markdown("### 🛠️ Silicon Architect")
48
  new_btn = gr.Button("➕ New Chat", variant="primary")
49
 
 
50
  history_list = gr.Dataset(
51
  components=[gr.Textbox(visible=False)],
52
  label="Recent Conversations",
53
- samples=[],
54
  type="values",
55
  samples_per_page=20
56
  )
57
 
58
  # --- Center Panel: Main Core Multimodal Chat ---
59
  with gr.Column(scale=3):
 
60
  chatbot = gr.Chatbot(show_label=False, height=700)
61
-
 
 
 
 
 
 
 
 
 
 
 
 
62
  chat_input = gr.Textbox(
63
  interactive=True,
64
  placeholder="Discuss architecture, paste code blocks, or ask CoderG to produce course documentation...",
65
  show_label=False,
66
- lines=1,
67
- max_lines=10,
68
  scale=8,
69
- submit_btn=False
70
  )
71
 
 
 
72
  upload_btn = gr.UploadButton(
73
  "📎 Attach Documents/Images",
74
  file_count="multiple",
@@ -76,6 +77,7 @@ with gr.Blocks() as demo:
76
  scale=2
77
  )
78
 
 
79
  upload_status = gr.Markdown("")
80
 
81
  # --- Right Panel: Agentic Control Tower ---
@@ -102,49 +104,47 @@ with gr.Blocks() as demo:
102
  gr.Markdown("#### 📊 Deployment Telemetry Logs")
103
  output_log = gr.Markdown("_Awaiting local environment staging completion..._")
104
 
105
- # --- UI ROUTING HANDLERS ---
106
- def handle_workspace_unlock(password_attempt):
107
- """Processes login, clears security gate block, and directly forces dataset rendering."""
108
- target_password = get_secret_password()
109
- clean_attempt = str(password_attempt).strip() if password_attempt is not None else ""
110
-
111
- # 1. Reject invalid credentials explicitly if password environment variable is active
112
- if target_password and clean_attempt and clean_attempt != target_password:
113
- raise gr.Error("❌ Invalid security token entered. Access to environment denied.")
114
-
115
- # 2. Extract history context (yields data array if valid, empty array if user opted to skip with blank)
116
- loaded_samples = []
117
- if not target_password or clean_attempt == target_password:
118
- if clean_attempt: # If they actually filled out the correct password, give them history
119
- loaded_samples = load_history()
120
-
121
- # 3. Collapse the auth widget and seamlessly return the sample payload
122
- return gr.update(visible=False), gr.update(samples=loaded_samples)
123
 
124
- # --- CORE WORKSPACE LOGIC ---
125
  def handle_file_upload(uploaded_files, current_staged_files):
 
126
  if not current_staged_files:
127
  current_staged_files = []
 
 
128
  for file_obj in uploaded_files:
 
129
  file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
130
  if file_path and file_path not in current_staged_files:
131
  current_staged_files.append(file_path)
 
 
132
  status_msg = f"🟢 **{len(current_staged_files)} file(s) staged successfully and attached to next prompt.**"
133
  return current_staged_files, status_msg
134
 
135
  def bot_response(message, history, chat_id):
136
  user_content = message["text"]
 
 
 
137
  clean_history_snapshot = list(history)
 
 
138
  history.append({"role": "user", "content": user_content})
139
  history.append({"role": "assistant", "content": ""})
140
 
 
141
  for partial_resp in chat_function(message, clean_history_snapshot):
142
  history[-1]["content"] = partial_resp
143
  yield history
144
 
145
  def handle_save(history, chat_id):
 
146
  new_id = save_chat(chat_id, history)
 
147
  current_list = load_history()
 
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,60 +155,108 @@ with gr.Blocks() as demo:
155
  return content, chat_id
156
 
157
  def push_authorized(repo_name, commit_msg, files_list):
 
 
158
  files = [f.strip() for f in files_list.split(",") if f.strip()]
 
159
  if not repo_name.strip():
160
  yield "❌ **Deployment Aborted:** Repository name cannot be empty."
161
  return
 
162
  yield "◌ _Connecting to GitHub REST API Engine..._"
163
  result = manage_github_repo(repo_name.strip(), commit_msg, files)
164
  yield f"{result}"
165
 
166
- # ==================== EVENT BINDING LIFECYCLES ====================
167
- login_btn.click(
168
- fn=handle_workspace_unlock,
169
- inputs=[password_input],
170
- outputs=[login_layout, history_list]
171
- )
172
 
 
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
  def process_submission(message_text, current_staged_files, history, chat_id):
180
  if not message_text.strip() and not current_staged_files:
181
  return history, "", current_staged_files, ""
182
- payload = {"text": message_text, "files": current_staged_files}
 
 
 
 
 
 
 
183
  for updated_history in bot_response(payload, history, chat_id):
 
184
  yield updated_history, "", [], ""
185
 
 
186
  chat_input.submit(
187
  fn=process_submission,
188
  inputs=[chat_input, staged_files_state, chatbot, chat_id_state],
189
  outputs=[chatbot, chat_input, staged_files_state, upload_status]
190
  ).then(
191
  fn=handle_save,
192
- inputs=[chatbot, chat_id_state],
193
  outputs=[chat_id_state, history_list]
194
  )
195
 
 
196
  history_list.click(
197
  fn=load_past_chat,
198
  inputs=[history_list],
199
  outputs=[chatbot, chat_id_state]
200
  )
201
 
 
202
  new_btn.click(
203
  fn=lambda: ([], "", [], load_history(), "", "_Awaiting local environment staging completion..._"),
204
  inputs=None,
205
  outputs=[chatbot, chat_id_state, staged_files_state, history_list, upload_status, output_log]
206
  )
207
 
 
208
  approve_btn.click(
209
  fn=push_authorized,
210
  inputs=[target_repo, commit_txt, staged_files],
211
  outputs=[output_log]
212
  )
213
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  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")
app_02.py CHANGED
@@ -1,75 +1,72 @@
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 +74,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 +100,49 @@ 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 +153,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
+
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
  scale=2
75
  )
76
 
 
77
  upload_status = gr.Markdown("")
78
 
79
  # --- Right Panel: Agentic Control Tower ---
 
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
  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")
storage.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
@@ -17,70 +17,23 @@ HISTORY_DIR = "./chathistory"
17
  # Initialize the API with your token
18
  api = HfApi(token=os.getenv("HF_TOKEN"))
19
 
20
- def get_secret_password():
21
- """Dynamically reads the environment secret to prevent cached empty variables at startup."""
22
- val = os.getenv("APP_PASSWORD")
23
-
24
- print(f"\nlen val: {len(val)}\n")
25
-
26
- return str(val).strip() if val is not None else ""
27
-
28
- def verify_access_token(user_password_input):
29
- """Validates input credentials directly against the environment variable string."""
30
- target_password = get_secret_password()
31
- clean_input = str(user_password_input).strip()
32
-
33
- print(f"len target_password: {len(target_password)}, len clean_input: {len(clean_input)}")
34
-
35
- if not target_password:
36
- return True
37
- return clean_input == target_password
38
-
39
- def load_history():
40
- """Retrieves list of chat IDs from the Hub, explicitly sorted by true modification time."""
41
- try:
42
- # Fetch rich metadata for chronological sorting
43
- repo_info = api.repo_info(repo_id=REPO_ID, repo_type="dataset", files_metadata=True)
44
-
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
- # Sort chronologically with true latest updates positioned at list index [0]
55
- chat_files_meta.sort(key=lambda x: x[1] if x[1] is not None else x[0], reverse=True)
56
- return [[item[0]] for item in chat_files_meta]
57
-
58
- except Exception as metadata_error:
59
- print(f"[!] Primary metadata pipeline exception: {metadata_error}. Launching clean fallback channel...")
60
-
61
- try:
62
- fallback_api = HfApi(token=os.getenv("HF_TOKEN"))
63
- files = fallback_api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
64
- chat_files = [f.split("/")[-1].replace(".json", "") for f in files if f.startswith("chats/")]
65
- return [[f] for f in sorted(chat_files, reverse=True)]
66
- except Exception as fallback_error:
67
- print(f"[!!] Critical Storage Interface Breakdown: {fallback_error}")
68
- return []
69
-
70
  def save_chat(chat_id, history):
71
  """Saves chat to local subdirectory and syncs to Hugging Face Dataset."""
72
  if not os.path.exists(HISTORY_DIR):
73
  os.makedirs(HISTORY_DIR)
74
 
 
75
  if not chat_id:
76
  chat_id = datetime.now().strftime("%m%d%Y_%H%M%S")
77
 
78
  filename = f"{chat_id}.json"
79
  local_path = os.path.join(HISTORY_DIR, filename)
80
 
 
81
  with open(local_path, "w", encoding="utf-8") as f:
82
  json.dump(history, f, indent=4)
83
 
 
84
  try:
85
  api.upload_file(
86
  path_or_fileobj=local_path,
@@ -93,15 +46,67 @@ def save_chat(chat_id, history):
93
 
94
  return chat_id
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  def get_chat_content(chat_id):
97
  """Loads a specific chat's content from the Hub or local cache."""
98
  filename = f"chats/{chat_id}.json"
99
  local_path = os.path.join(HISTORY_DIR, f"{chat_id}.json")
100
 
101
  try:
 
102
  if not os.path.exists(HISTORY_DIR):
103
  os.makedirs(HISTORY_DIR)
104
 
 
 
105
  downloaded_path = hf_hub_download(
106
  repo_id=REPO_ID,
107
  repo_type="dataset",
@@ -111,6 +116,7 @@ def get_chat_content(chat_id):
111
  with open(downloaded_path, "r", encoding="utf-8") as f:
112
  return json.load(f)
113
  except Exception:
 
114
  if os.path.exists(local_path):
115
  with open(local_path, "r", encoding="utf-8") as f:
116
  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
 
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,
 
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",
 
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)
storage_00.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
@@ -14,86 +14,103 @@ 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):
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,12 +118,9 @@ def get_chat_content(chat_id):
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,7 +130,6 @@ def get_chat_content(chat_id):
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)
 
1
 
2
+ # ./storage.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
+ # 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
  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
  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)