ArunKr commited on
Commit
496e03d
·
verified ·
1 Parent(s): efb3d66

Upload folder using huggingface_hub

Browse files
TASKS.md CHANGED
@@ -67,7 +67,7 @@ Legend:
67
  - [x] Add lint/tests + CI (`ruff`, `pytest`, `.github/workflows/ci.yml`).
68
 
69
  ## P2/P3 — UI consolidation (nice-to-have)
70
- - [ ] Merge Autonomous mode and chat mode to a single chat UI.
71
 
72
  ## P2 — Auth UX
73
  - [x] Password reset by email (Supabase recovery flow).
 
67
  - [x] Add lint/tests + CI (`ruff`, `pytest`, `.github/workflows/ci.yml`).
68
 
69
  ## P2/P3 — UI consolidation (nice-to-have)
70
+ - [x] Merge Autonomous mode and chat mode to a single chat UI.
71
 
72
  ## P2 — Auth UX
73
  - [x] Password reset by email (Supabase recovery flow).
app/routes/base.py CHANGED
@@ -58,6 +58,11 @@ async def read_admin():
58
  return FileResponse(str(_STATIC / "dashboard.html"))
59
 
60
 
 
 
 
 
 
61
  @router.get("/health")
62
  async def health_check():
63
  return {"status": "ok"}
 
58
  return FileResponse(str(_STATIC / "dashboard.html"))
59
 
60
 
61
+ @router.get("/docs")
62
+ async def read_docs():
63
+ return FileResponse(str(_STATIC / "docs.html"))
64
+
65
+
66
  @router.get("/health")
67
  async def health_check():
68
  return {"status": "ok"}
static/dashboard.html CHANGED
@@ -676,120 +676,49 @@
676
  </div>
677
  </div>
678
 
679
- <div id="chat-standard" class="flex-grow flex flex-col min-h-0">
680
- <!-- Messages -->
681
- <div id="chat-history" class="flex-grow p-4 overflow-y-auto space-y-4 scroll-smooth">
682
- <div id="chat-empty-state" class="text-center text-gray-500 mt-10">
683
- <div class="text-lg font-semibold text-gray-300">Start a conversation</div>
684
- <div class="text-sm text-gray-500 mt-1">Pick a provider in Settings, then send a message.</div>
685
  </div>
686
- </div>
687
-
688
- <!-- Input -->
689
- <div class="p-3 bg-gray-800 border-t border-gray-700 shrink-0">
690
- <div id="chat-attachments" class="max-w-5xl mx-auto mb-2 hidden"></div>
691
- <div class="flex space-x-2 max-w-5xl mx-auto items-center">
692
- <select id="chat-agent-target" onchange="setChatAgentTarget(this.value)"
693
- class="bg-gray-700 h-10 rounded-lg border border-gray-600 px-2 text-sm text-white outline-none focus:border-blue-500"
694
- title="Target">
695
- <option value="chat">Chat</option>
696
- <option value="codex">@codex</option>
697
- </select>
698
- <textarea id="chat-input" rows="1"
699
- class="flex-grow bg-gray-700 px-3 py-2 rounded-lg border border-gray-600 resize-none focus:ring-2 focus:ring-blue-500 focus:outline-none text-white max-h-32"
700
- placeholder="Message..."
701
- oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"
702
- onkeydown="handleEnter(event)"></textarea>
703
- <input id="chat-model-quick" type="text" list="model-list" placeholder="Model"
704
- class="bg-gray-700 h-10 w-44 px-2 rounded-lg border border-gray-600 text-sm text-white outline-none focus:border-blue-500 hidden sm:block"
705
- onchange="setQuickModel(this.value)" title="Model" />
706
- <input id="chat-file-input" type="file" accept="image/*" multiple class="hidden" />
707
- <button onclick="document.getElementById('chat-file-input').click()"
708
- class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
709
- title="Attach images" aria-label="Attach images">
710
- <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
711
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21.44 11.05l-8.49 8.49a5 5 0 0 1-7.07-7.07l9.2-9.2a3.5 3.5 0 0 1 4.95 4.95l-9.2 9.2a2 2 0 0 1-2.83-2.83l8.49-8.49" />
712
- </svg>
713
- </button>
714
- <button onclick="captureScreenshotToChat()"
715
- class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
716
- title="Take screenshot" aria-label="Take screenshot">
717
- <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
718
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7h4l2-2h4l2 2h4v12H4V7z" />
719
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z" />
720
- </svg>
721
- </button>
722
- <button id="chat-mic-btn" onclick="toggleChatDictation()"
723
- class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
724
- title="Voice input" aria-label="Voice input">
725
- <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
726
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v5a3 3 0 0 0 3 3z" />
727
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 0 1-14 0" />
728
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19v3" />
729
- </svg>
730
- </button>
731
- <button id="chat-stop-btn" onclick="stopChatGeneration()" disabled
732
- class="bg-gray-600 h-10 px-3 rounded-lg hover:bg-gray-700 font-bold transition inline-flex items-center justify-center disabled:opacity-40 disabled:cursor-not-allowed"
733
- title="Stop generating">
734
- <svg class="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
735
- <rect x="7" y="7" width="10" height="10" rx="2" />
736
- </svg>
737
- Stop
738
- </button>
739
- <button onclick="sendChatBarMessage()"
740
- class="bg-blue-600 h-10 w-10 rounded-lg hover:bg-blue-700 font-bold transition inline-flex items-center justify-center">
741
- <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
742
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
743
- d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
744
- </svg>
745
- </button>
746
- </div>
747
- </div>
748
- </div>
749
-
750
- <!-- Autonomous mode embedded in Chat view -->
751
- <div id="chat-autonomous" class="hidden flex-grow min-h-0 flex flex-col">
752
- <div id="agent-split-root" class="split-root">
753
- <div id="agent-pane-chat" class="split-pane min-h-0 flex flex-col border-r border-gray-700 agent-pane-surface">
754
- <div id="agent-terminal-collapsed-bar" class="hidden px-3 py-2 border-b border-gray-700/70 flex items-center justify-end bg-gray-900/30">
755
- <button onclick="toggleAgentTerminalCollapsed()"
756
- class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition"
757
- title="Show terminal">Show terminal</button>
758
- </div>
759
- <div class="flex-grow min-h-0 overflow-hidden">
760
- <div id="agent-chat-history" class="h-full p-4 overflow-y-auto space-y-4">
761
- <div id="agent-empty-state" class="text-center text-gray-500 mt-10">
762
- <div class="text-lg font-semibold text-gray-300">Autonomous mode</div>
763
- <div class="text-sm text-gray-500 mt-1">Ask the agent, then use “Run” on code blocks.</div>
764
- </div>
765
  </div>
766
  </div>
 
 
767
  <div class="p-3 bg-gray-800 border-t border-gray-700 shrink-0">
768
- <div id="agent-attachments" class="mb-2 hidden"></div>
769
- <div class="flex space-x-2 items-center">
770
- <select id="agent-agent-target" onchange="setAgentUseCodexCli(this.value === 'codex')"
771
  class="bg-gray-700 h-10 rounded-lg border border-gray-600 px-2 text-sm text-white outline-none focus:border-blue-500"
772
  title="Target">
773
- <option value="agent">Agent</option>
774
  <option value="codex">@codex</option>
775
  </select>
776
- <textarea id="agent-chat-input" rows="1"
777
- class="flex-grow bg-gray-700 px-3 py-2 rounded-lg border border-gray-600 resize-none focus:ring-2 focus:ring-blue-500 focus:outline-none text-white max-h-28"
778
- placeholder="Ask agent..."
779
  oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"
780
- onkeydown="handleAgentEnter(event)"></textarea>
781
- <input id="agent-model-quick" type="text" list="model-list" placeholder="Model"
782
  class="bg-gray-700 h-10 w-44 px-2 rounded-lg border border-gray-600 text-sm text-white outline-none focus:border-blue-500 hidden sm:block"
783
  onchange="setQuickModel(this.value)" title="Model" />
784
- <input id="agent-file-input" type="file" accept="image/*" multiple class="hidden" />
785
- <button onclick="document.getElementById('agent-file-input').click()"
786
  class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
787
  title="Attach images" aria-label="Attach images">
788
  <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
789
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21.44 11.05l-8.49 8.49a5 5 0 0 1-7.07-7.07l9.2-9.2a3.5 3.5 0 0 1 4.95 4.95l-9.2 9.2a2 2 0 0 1-2.83-2.83l8.49-8.49" />
790
  </svg>
791
  </button>
792
- <button onclick="captureScreenshotToAgent()"
793
  class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
794
  title="Take screenshot" aria-label="Take screenshot">
795
  <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
@@ -797,7 +726,7 @@
797
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z" />
798
  </svg>
799
  </button>
800
- <button id="agent-mic-btn" onclick="toggleAgentDictation()"
801
  class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
802
  title="Voice input" aria-label="Voice input">
803
  <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
@@ -806,17 +735,16 @@
806
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19v3" />
807
  </svg>
808
  </button>
809
- <button id="agent-stop-btn" onclick="stopAgentGeneration()" disabled
810
- class="bg-gray-600 h-10 px-3 rounded-lg hover:bg-gray-700 font-bold transition flex items-center disabled:opacity-40 disabled:cursor-not-allowed"
811
  title="Stop generating">
812
  <svg class="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
813
  <rect x="7" y="7" width="10" height="10" rx="2" />
814
  </svg>
815
  Stop
816
  </button>
817
- <button onclick="sendAgentMessage()"
818
- class="bg-blue-600 h-10 w-10 rounded-lg hover:bg-blue-700 font-bold transition inline-flex items-center justify-center"
819
- title="Send" aria-label="Send">
820
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
821
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
822
  d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
@@ -825,19 +753,19 @@
825
  </div>
826
  </div>
827
  </div>
828
- <div id="agent-split-divider" class="split-divider" title="Drag to resize panes"></div>
829
- <div id="agent-pane-terminal" class="split-pane min-h-0 agent-terminal-chrome flex flex-col">
830
- <div class="shrink-0 flex items-center bg-gray-900/60 border-b border-gray-700 overflow-x-auto">
831
- <div id="agent-terminal-tabs" class="flex"></div>
832
- <button onclick="toggleAgentTerminalCollapsed()"
833
- class="ml-auto px-2 py-2 text-gray-300 hover:text-white hover:bg-white/10 transition font-bold"
834
- title="Collapse terminal" aria-label="Collapse terminal">⟷</button>
835
- <button onclick="createTerminalTab({ scope: 'agent' })"
836
- class="px-3 py-2 text-gray-300 hover:text-white hover:bg-white/10 transition font-bold"
837
- title="New Terminal">+</button>
838
- </div>
839
- <div id="agent-terminals-container" class="flex-grow relative w-full h-full"></div>
840
  </div>
 
841
  </div>
842
  </div>
843
  </div>
 
676
  </div>
677
  </div>
678
 
679
+ <div id="agent-split-root" class="split-root flex-grow min-h-0">
680
+ <div id="agent-pane-chat" class="split-pane min-h-0 flex flex-col border-r border-gray-700 agent-pane-surface">
681
+ <div id="agent-terminal-collapsed-bar" class="hidden px-3 py-2 border-b border-gray-700/70 flex items-center justify-end bg-gray-900/30">
682
+ <button onclick="toggleAgentTerminalCollapsed()"
683
+ class="px-2 py-1 text-xs rounded-md text-gray-200 hover:bg-white/10 transition"
684
+ title="Show terminal">Show terminal</button>
685
  </div>
686
+ <div id="chat-standard" class="flex-grow flex flex-col min-h-0">
687
+ <!-- Messages -->
688
+ <div id="chat-history" class="flex-grow p-4 overflow-y-auto space-y-4 scroll-smooth">
689
+ <div id="chat-empty-state" class="text-center text-gray-500 mt-10">
690
+ <div class="text-lg font-semibold text-gray-300">Start a conversation</div>
691
+ <div class="text-sm text-gray-500 mt-1">Pick a provider in Settings, then send a message.</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  </div>
693
  </div>
694
+
695
+ <!-- Input -->
696
  <div class="p-3 bg-gray-800 border-t border-gray-700 shrink-0">
697
+ <div id="chat-attachments" class="max-w-5xl mx-auto mb-2 hidden"></div>
698
+ <div class="flex space-x-2 max-w-5xl mx-auto items-center">
699
+ <select id="chat-agent-target" onchange="setChatAgentTarget(this.value)"
700
  class="bg-gray-700 h-10 rounded-lg border border-gray-600 px-2 text-sm text-white outline-none focus:border-blue-500"
701
  title="Target">
702
+ <option value="chat">Chat</option>
703
  <option value="codex">@codex</option>
704
  </select>
705
+ <textarea id="chat-input" rows="1"
706
+ class="flex-grow bg-gray-700 px-3 py-2 rounded-lg border border-gray-600 resize-none focus:ring-2 focus:ring-blue-500 focus:outline-none text-white max-h-32"
707
+ placeholder="Message..."
708
  oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"
709
+ onkeydown="handleEnter(event)"></textarea>
710
+ <input id="chat-model-quick" type="text" list="model-list" placeholder="Model"
711
  class="bg-gray-700 h-10 w-44 px-2 rounded-lg border border-gray-600 text-sm text-white outline-none focus:border-blue-500 hidden sm:block"
712
  onchange="setQuickModel(this.value)" title="Model" />
713
+ <input id="chat-file-input" type="file" accept="image/*" multiple class="hidden" />
714
+ <button onclick="document.getElementById('chat-file-input').click()"
715
  class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
716
  title="Attach images" aria-label="Attach images">
717
  <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
718
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21.44 11.05l-8.49 8.49a5 5 0 0 1-7.07-7.07l9.2-9.2a3.5 3.5 0 0 1 4.95 4.95l-9.2 9.2a2 2 0 0 1-2.83-2.83l8.49-8.49" />
719
  </svg>
720
  </button>
721
+ <button onclick="captureScreenshotToChat()"
722
  class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
723
  title="Take screenshot" aria-label="Take screenshot">
724
  <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
 
726
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z" />
727
  </svg>
728
  </button>
729
+ <button id="chat-mic-btn" onclick="toggleChatDictation()"
730
  class="bg-gray-700 h-10 w-10 rounded-lg hover:bg-gray-600 transition inline-flex items-center justify-center text-gray-200"
731
  title="Voice input" aria-label="Voice input">
732
  <svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
 
735
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19v3" />
736
  </svg>
737
  </button>
738
+ <button id="chat-stop-btn" onclick="stopChatGeneration()" disabled
739
+ class="bg-gray-600 h-10 px-3 rounded-lg hover:bg-gray-700 font-bold transition inline-flex items-center justify-center disabled:opacity-40 disabled:cursor-not-allowed"
740
  title="Stop generating">
741
  <svg class="w-4 h-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
742
  <rect x="7" y="7" width="10" height="10" rx="2" />
743
  </svg>
744
  Stop
745
  </button>
746
+ <button onclick="sendChatBarMessage()"
747
+ class="bg-blue-600 h-10 w-10 rounded-lg hover:bg-blue-700 font-bold transition inline-flex items-center justify-center">
 
748
  <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
749
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
750
  d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
 
753
  </div>
754
  </div>
755
  </div>
756
+ </div>
757
+ <div id="agent-split-divider" class="split-divider hidden" title="Drag to resize panes"></div>
758
+ <div id="agent-pane-terminal" class="split-pane hidden min-h-0 agent-terminal-chrome flex flex-col">
759
+ <div class="shrink-0 flex items-center bg-gray-900/60 border-b border-gray-700 overflow-x-auto">
760
+ <div id="agent-terminal-tabs" class="flex"></div>
761
+ <button onclick="toggleAgentTerminalCollapsed()"
762
+ class="ml-auto px-2 py-2 text-gray-300 hover:text-white hover:bg-white/10 transition font-bold"
763
+ title="Collapse terminal" aria-label="Collapse terminal">⟷</button>
764
+ <button onclick="createTerminalTab({ scope: 'agent' })"
765
+ class="px-3 py-2 text-gray-300 hover:text-white hover:bg-white/10 transition font-bold"
766
+ title="New Terminal">+</button>
 
767
  </div>
768
+ <div id="agent-terminals-container" class="flex-grow relative w-full h-full"></div>
769
  </div>
770
  </div>
771
  </div>
static/dashboard.js CHANGED
@@ -133,7 +133,7 @@ let supabase;
133
  const cmd = codeEl.innerText.replace(/\n+$/, '');
134
  if (!cmd) return;
135
  const agentVisible = !document.getElementById('chat-view').classList.contains('hidden')
136
- && !document.getElementById('chat-autonomous').classList.contains('hidden');
137
  await runInTerminal(cmd + '\n', { preferScope: agentVisible ? 'agent' : 'main', autoShow: true });
138
  };
139
 
@@ -347,6 +347,15 @@ let supabase;
347
  return new URL(cleaned, base).toString();
348
  }
349
 
 
 
 
 
 
 
 
 
 
350
  async function fetchConfig() {
351
  const candidates = ['config', 'api/config'];
352
  let lastError = null;
@@ -1037,7 +1046,7 @@ let supabase;
1037
  window.__supabaseClient = supabase;
1038
 
1039
  const { data: { session } } = await supabase.auth.getSession();
1040
- if (!session) { window.location.href = routeUrl('login'); return; }
1041
  window.__sbAccessToken = (session.access_token || '').trim();
1042
  supabase.auth.onAuthStateChange((_event, nextSession) => {
1043
  window.__sbAccessToken = (nextSession?.access_token || '').trim();
@@ -1610,6 +1619,7 @@ let supabase;
1610
  const divider = document.getElementById('agent-split-divider');
1611
  if (!root || !paneChat || !paneTerminal || !divider) return;
1612
 
 
1613
  const key = 'agent_split_ratio_v1';
1614
  const collapseKey = 'agent_terminal_collapsed_v1';
1615
  const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
@@ -1635,6 +1645,9 @@ let supabase;
1635
  paneChat.style.flex = '1 1 auto';
1636
  }
1637
 
 
 
 
1638
  let dragging = false;
1639
  const onMove = (clientX) => {
1640
  const rect = root.getBoundingClientRect();
@@ -1694,25 +1707,40 @@ let supabase;
1694
  }
1695
 
1696
  function setChatMode(mode) {
1697
- const standard = document.getElementById('chat-standard');
1698
- const auto = document.getElementById('chat-autonomous');
 
1699
  const btnChat = document.getElementById('chat-mode-chat');
1700
  const btnAuto = document.getElementById('chat-mode-auto');
1701
- if (!standard || !auto) return;
1702
  const m = mode === 'autonomous' ? 'autonomous' : 'chat';
1703
 
1704
- standard.classList.toggle('hidden', m === 'autonomous');
1705
- auto.classList.toggle('hidden', m !== 'autonomous');
 
 
 
 
 
1706
  if (btnChat) btnChat.classList.toggle('bg-white/10', m === 'chat');
1707
  if (btnAuto) btnAuto.classList.toggle('bg-white/10', m === 'autonomous');
1708
 
1709
  localStorage.setItem('chat_mode_v1', m);
1710
  if (m === 'autonomous') {
1711
  if (!activeAgentTerminalId) activeAgentTerminalId = createTerminalTab({ scope: 'agent' });
1712
- setTimeout(() => {
1713
- initAgentSplitPane();
1714
- if (activeAgentTerminalId) fitTerm(activeAgentTerminalId);
1715
- }, 0);
 
 
 
 
 
 
 
 
 
1716
  }
1717
  }
1718
 
@@ -1724,7 +1752,7 @@ let supabase;
1724
 
1725
  window.addEventListener('resize', () => {
1726
  if (activeTerminalId && !document.getElementById('terminal-view').classList.contains('hidden')) fitTerm(activeTerminalId);
1727
- if (activeAgentTerminalId && !document.getElementById('chat-view').classList.contains('hidden') && !document.getElementById('chat-autonomous').classList.contains('hidden')) fitTerm(activeAgentTerminalId);
1728
  });
1729
 
1730
  // --- Chat Logic ---
 
133
  const cmd = codeEl.innerText.replace(/\n+$/, '');
134
  if (!cmd) return;
135
  const agentVisible = !document.getElementById('chat-view').classList.contains('hidden')
136
+ && !document.getElementById('agent-pane-terminal')?.classList.contains('hidden');
137
  await runInTerminal(cmd + '\n', { preferScope: agentVisible ? 'agent' : 'main', autoShow: true });
138
  };
139
 
 
347
  return new URL(cleaned, base).toString();
348
  }
349
 
350
+ function loginUrlWithNext(nextPath) {
351
+ const u = new URL(routeUrl('login'));
352
+ const next = String(nextPath || '').trim();
353
+ if (next && next.startsWith('/') && !next.startsWith('//') && !next.includes('\\')) {
354
+ u.searchParams.set('next', next);
355
+ }
356
+ return u.toString();
357
+ }
358
+
359
  async function fetchConfig() {
360
  const candidates = ['config', 'api/config'];
361
  let lastError = null;
 
1046
  window.__supabaseClient = supabase;
1047
 
1048
  const { data: { session } } = await supabase.auth.getSession();
1049
+ if (!session) { window.location.href = loginUrlWithNext(location.pathname || '/app'); return; }
1050
  window.__sbAccessToken = (session.access_token || '').trim();
1051
  supabase.auth.onAuthStateChange((_event, nextSession) => {
1052
  window.__sbAccessToken = (nextSession?.access_token || '').trim();
 
1619
  const divider = document.getElementById('agent-split-divider');
1620
  if (!root || !paneChat || !paneTerminal || !divider) return;
1621
 
1622
+ const alreadyInited = root.dataset.splitInited === '1';
1623
  const key = 'agent_split_ratio_v1';
1624
  const collapseKey = 'agent_terminal_collapsed_v1';
1625
  const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
 
1645
  paneChat.style.flex = '1 1 auto';
1646
  }
1647
 
1648
+ if (alreadyInited) return;
1649
+ root.dataset.splitInited = '1';
1650
+
1651
  let dragging = false;
1652
  const onMove = (clientX) => {
1653
  const rect = root.getBoundingClientRect();
 
1707
  }
1708
 
1709
  function setChatMode(mode) {
1710
+ const paneChat = document.getElementById('agent-pane-chat');
1711
+ const paneTerminal = document.getElementById('agent-pane-terminal');
1712
+ const divider = document.getElementById('agent-split-divider');
1713
  const btnChat = document.getElementById('chat-mode-chat');
1714
  const btnAuto = document.getElementById('chat-mode-auto');
1715
+ if (!paneChat || !paneTerminal || !divider) return;
1716
  const m = mode === 'autonomous' ? 'autonomous' : 'chat';
1717
 
1718
+ // Chat always stays visible; Autonomous mode just reveals the split terminal pane.
1719
+ const collapsed = localStorage.getItem('agent_terminal_collapsed_v1') === '1';
1720
+ const showTerminal = m === 'autonomous' && !collapsed;
1721
+ paneTerminal.classList.toggle('hidden', !showTerminal);
1722
+ divider.classList.toggle('hidden', !showTerminal);
1723
+ const bar = document.getElementById('agent-terminal-collapsed-bar');
1724
+ if (bar) bar.classList.toggle('hidden', showTerminal || m !== 'autonomous');
1725
  if (btnChat) btnChat.classList.toggle('bg-white/10', m === 'chat');
1726
  if (btnAuto) btnAuto.classList.toggle('bg-white/10', m === 'autonomous');
1727
 
1728
  localStorage.setItem('chat_mode_v1', m);
1729
  if (m === 'autonomous') {
1730
  if (!activeAgentTerminalId) activeAgentTerminalId = createTerminalTab({ scope: 'agent' });
1731
+ if (showTerminal) {
1732
+ setTimeout(() => {
1733
+ initAgentSplitPane();
1734
+ if (activeAgentTerminalId) fitTerm(activeAgentTerminalId);
1735
+ }, 0);
1736
+ }
1737
+ const input = document.getElementById('chat-input');
1738
+ if (input) input.placeholder = 'Ask agent…';
1739
+ }
1740
+ if (m === 'chat') {
1741
+ const input = document.getElementById('chat-input');
1742
+ if (input) input.placeholder = 'Message...';
1743
+ paneChat.style.flex = '1 1 auto';
1744
  }
1745
  }
1746
 
 
1752
 
1753
  window.addEventListener('resize', () => {
1754
  if (activeTerminalId && !document.getElementById('terminal-view').classList.contains('hidden')) fitTerm(activeTerminalId);
1755
+ if (activeAgentTerminalId && !document.getElementById('chat-view').classList.contains('hidden') && !document.getElementById('agent-pane-terminal')?.classList.contains('hidden')) fitTerm(activeAgentTerminalId);
1756
  });
1757
 
1758
  // --- Chat Logic ---
static/docs.html ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>autonomy-labs · Documentation</title>
8
+ <script src="https://cdn.tailwindcss.com?plugins=typography"></script>
9
+ <link rel="stylesheet" href="/static/theme.css">
10
+ </head>
11
+
12
+ <body class="min-h-screen bg-gradient-to-b from-gray-950 via-gray-900 to-gray-950 text-white">
13
+ <header class="border-b border-white/10">
14
+ <div class="mx-auto max-w-4xl px-5 py-4 flex items-center justify-between">
15
+ <div class="flex items-center gap-3">
16
+ <div class="h-9 w-9 rounded-xl bg-blue-600/20 border border-blue-500/30 flex items-center justify-center">
17
+ <span class="text-blue-300 font-black">A</span>
18
+ </div>
19
+ <div class="leading-tight">
20
+ <div class="font-semibold tracking-tight">autonomy-labs</div>
21
+ <div class="text-xs text-gray-400">docs · setup · troubleshooting</div>
22
+ </div>
23
+ </div>
24
+ <div class="flex items-center gap-2">
25
+ <a href="/login"
26
+ class="px-3 py-2 rounded-lg text-sm text-gray-200 hover:text-white hover:bg-white/5 border border-white/10">Login</a>
27
+ <a href="/login?next=%2Fapp"
28
+ class="px-3 py-2 rounded-lg text-sm bg-blue-600 hover:bg-blue-700 font-semibold border border-blue-500/40">Open
29
+ App</a>
30
+ </div>
31
+ </div>
32
+ </header>
33
+
34
+ <main class="mx-auto max-w-4xl px-5 py-10">
35
+ <div class="prose prose-invert max-w-none">
36
+ <h1>Get started</h1>
37
+ <p>This app uses <strong>Supabase Auth</strong>. In Hugging Face Spaces, configure secrets first.</p>
38
+
39
+ <h2>Hugging Face Spaces setup</h2>
40
+ <ol>
41
+ <li>Go to <strong>Settings → Variables and secrets → Secrets</strong>.</li>
42
+ <li>Set:
43
+ <ul>
44
+ <li><code>SUPABASE_URL</code></li>
45
+ <li><code>SUPABASE_KEY</code> (anon key) or <code>SUPABASE_ANON_KEY</code></li>
46
+ </ul>
47
+ </li>
48
+ <li>Restart the Space.</li>
49
+ </ol>
50
+
51
+ <h2>Login / Register</h2>
52
+ <ul>
53
+ <li>Open <a href="/login">/login</a> and sign in.</li>
54
+ <li>To return to the app automatically, use <a href="/login?next=%2Fapp">/login?next=/app</a>.</li>
55
+ </ul>
56
+
57
+ <h2>Password reset</h2>
58
+ <ul>
59
+ <li>Ensure Supabase Auth redirect URLs include <code>/login</code> for your Space domain.</li>
60
+ <li>The login page supports both recovery link formats (<code>#access_token=…</code> and <code>?code=…</code>).</li>
61
+ </ul>
62
+
63
+ <h2>More docs</h2>
64
+ <ul>
65
+ <li><a href="https://github.com/Akt-AI/autonomy-labs/blob/main/README.md" target="_blank" rel="noreferrer">README</a></li>
66
+ <li><a href="https://github.com/Akt-AI/autonomy-labs/blob/main/docs/TROUBLESHOOTING.md" target="_blank" rel="noreferrer">Troubleshooting</a></li>
67
+ <li><a href="https://github.com/Akt-AI/autonomy-labs/blob/main/docs/SECURITY_DEPLOYMENT.md" target="_blank" rel="noreferrer">Security deployment</a></li>
68
+ </ul>
69
+ </div>
70
+ </main>
71
+ </body>
72
+
73
+ </html>
74
+
static/landing.html CHANGED
@@ -24,7 +24,7 @@
24
  <div class="flex items-center gap-2">
25
  <a href="/login"
26
  class="px-3 py-2 rounded-lg text-sm text-gray-200 hover:text-white hover:bg-white/5 border border-white/10">Login</a>
27
- <a href="/app"
28
  class="px-3 py-2 rounded-lg text-sm bg-blue-600 hover:bg-blue-700 font-semibold border border-blue-500/40">Open
29
  App</a>
30
  </div>
@@ -48,10 +48,10 @@
48
  Designed for Hugging Face Spaces and local development.
49
  </p>
50
  <div class="mt-7 flex flex-wrap gap-3">
51
- <a href="/login"
52
  class="px-5 py-3 rounded-xl bg-blue-600 hover:bg-blue-700 font-semibold border border-blue-500/40">Get
53
  started</a>
54
- <a href="/app"
55
  class="px-5 py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-200">Open app</a>
56
  <a href="/health"
57
  class="px-5 py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-200">Health
@@ -102,4 +102,3 @@
102
  </body>
103
 
104
  </html>
105
-
 
24
  <div class="flex items-center gap-2">
25
  <a href="/login"
26
  class="px-3 py-2 rounded-lg text-sm text-gray-200 hover:text-white hover:bg-white/5 border border-white/10">Login</a>
27
+ <a href="/login?next=%2Fapp"
28
  class="px-3 py-2 rounded-lg text-sm bg-blue-600 hover:bg-blue-700 font-semibold border border-blue-500/40">Open
29
  App</a>
30
  </div>
 
48
  Designed for Hugging Face Spaces and local development.
49
  </p>
50
  <div class="mt-7 flex flex-wrap gap-3">
51
+ <a href="/docs"
52
  class="px-5 py-3 rounded-xl bg-blue-600 hover:bg-blue-700 font-semibold border border-blue-500/40">Get
53
  started</a>
54
+ <a href="/login?next=%2Fapp"
55
  class="px-5 py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-200">Open app</a>
56
  <a href="/health"
57
  class="px-5 py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-200">Health
 
102
  </body>
103
 
104
  </html>
 
static/login.js CHANGED
@@ -81,6 +81,26 @@ var supabaseReady = false;
81
  return new URL(cleaned, base).toString();
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  function replaceUrl(path) {
85
  try { window.location.replace(routeUrl(path)); } catch { window.location.href = routeUrl(path); }
86
  }
@@ -157,14 +177,14 @@ var supabaseReady = false;
157
  }
158
  const recovery = isRecoveryUrl();
159
  if (session && !recovery) {
160
- window.location.href = routeUrl('app');
161
  }
162
  });
163
 
164
  const recovery = consumed.isRecovery || isRecoveryUrl();
165
  const { data: { session } } = await supabase.auth.getSession();
166
  if (session && !recovery) {
167
- window.location.href = routeUrl('app');
168
  return;
169
  }
170
  if (recovery) {
@@ -246,8 +266,10 @@ var supabaseReady = false;
246
  if (type === 'login') {
247
  result = await supabase.auth.signInWithPassword({ email, password });
248
  } else {
249
- const redirect = `${window.location.origin}/login`;
250
- result = await supabase.auth.signUp({ email, password, options: { emailRedirectTo: redirect } });
 
 
251
  }
252
 
253
  if (result.error) throw result.error;
@@ -255,12 +277,12 @@ var supabaseReady = false;
255
  if (type === 'register') {
256
  if (result.data && result.data.session) {
257
  // Email verification is disabled, user is logged in
258
- window.location.href = routeUrl('app');
259
  } else {
260
  showAlert('Registration successful! Please check your email to verify (if enabled) or try logging in.', 'success');
261
  }
262
  } else {
263
- window.location.href = routeUrl('app');
264
  }
265
  } catch (error) {
266
  showAlert(error.message);
 
81
  return new URL(cleaned, base).toString();
82
  }
83
 
84
+ function safeNextPath() {
85
+ try {
86
+ const nextRaw = String(new URLSearchParams(String(window.location.search || '')).get('next') || '').trim();
87
+ if (!nextRaw) return '';
88
+ if (!nextRaw.startsWith('/')) return '';
89
+ if (nextRaw.startsWith('//')) return '';
90
+ if (nextRaw.includes('\\')) return '';
91
+ // Only allow paths within the app.
92
+ return nextRaw;
93
+ } catch {
94
+ return '';
95
+ }
96
+ }
97
+
98
+ function postAuthRedirectUrl() {
99
+ const next = safeNextPath();
100
+ if (next) return routeUrl(next.replace(/^\/+/, ''));
101
+ return routeUrl('app');
102
+ }
103
+
104
  function replaceUrl(path) {
105
  try { window.location.replace(routeUrl(path)); } catch { window.location.href = routeUrl(path); }
106
  }
 
177
  }
178
  const recovery = isRecoveryUrl();
179
  if (session && !recovery) {
180
+ window.location.href = postAuthRedirectUrl();
181
  }
182
  });
183
 
184
  const recovery = consumed.isRecovery || isRecoveryUrl();
185
  const { data: { session } } = await supabase.auth.getSession();
186
  if (session && !recovery) {
187
+ window.location.href = postAuthRedirectUrl();
188
  return;
189
  }
190
  if (recovery) {
 
266
  if (type === 'login') {
267
  result = await supabase.auth.signInWithPassword({ email, password });
268
  } else {
269
+ const redirect = new URL(`${window.location.origin}/login`);
270
+ const next = safeNextPath();
271
+ if (next) redirect.searchParams.set('next', next);
272
+ result = await supabase.auth.signUp({ email, password, options: { emailRedirectTo: redirect.toString() } });
273
  }
274
 
275
  if (result.error) throw result.error;
 
277
  if (type === 'register') {
278
  if (result.data && result.data.session) {
279
  // Email verification is disabled, user is logged in
280
+ window.location.href = postAuthRedirectUrl();
281
  } else {
282
  showAlert('Registration successful! Please check your email to verify (if enabled) or try logging in.', 'success');
283
  }
284
  } else {
285
+ window.location.href = postAuthRedirectUrl();
286
  }
287
  } catch (error) {
288
  showAlert(error.message);