Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- TASKS.md +1 -1
- app/routes/base.py +5 -0
- static/dashboard.html +43 -115
- static/dashboard.js +40 -12
- static/docs.html +74 -0
- static/landing.html +3 -4
- static/login.js +28 -6
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 |
-
- [
|
| 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="
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
</div>
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 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="
|
| 769 |
-
<div class="flex space-x-2 items-center">
|
| 770 |
-
<select id="
|
| 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="
|
| 774 |
<option value="codex">@codex</option>
|
| 775 |
</select>
|
| 776 |
-
<textarea id="
|
| 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-
|
| 778 |
-
placeholder="
|
| 779 |
oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"
|
| 780 |
-
onkeydown="
|
| 781 |
-
<input id="
|
| 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="
|
| 785 |
-
<button onclick="document.getElementById('
|
| 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="
|
| 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="
|
| 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="
|
| 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="
|
| 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 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 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('
|
| 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 =
|
| 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
|
| 1698 |
-
const
|
|
|
|
| 1699 |
const btnChat = document.getElementById('chat-mode-chat');
|
| 1700 |
const btnAuto = document.getElementById('chat-mode-auto');
|
| 1701 |
-
if (!
|
| 1702 |
const m = mode === 'autonomous' ? 'autonomous' : 'chat';
|
| 1703 |
|
| 1704 |
-
|
| 1705 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 1713 |
-
|
| 1714 |
-
|
| 1715 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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('
|
| 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="/
|
| 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="/
|
| 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="/
|
| 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 =
|
| 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 =
|
| 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 |
-
|
|
|
|
|
|
|
| 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 =
|
| 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 =
|
| 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);
|