History

better

Theo Browne committed 9 months ago

Commit 7a68e571 parent 99a90672 files · +108 −75Browse files

Unified · Split

2 files
  1. apps/renderer/src/components/ChatView.tsx +107 −0
  2. apps/renderer/src/components/Sidebar.tsx +1 −75
apps/renderer/src/components/ChatView.tsx+107−0
@@ -113,6 +113,7 @@ export default function ChatView() { · 112 unmodified lines
113113 const [isModelMenuOpen, setIsModelMenuOpen] = useState(false);
114114 const [selectedEffort, setSelectedEffort] =
115115 useState<string>(DEFAULT_REASONING);
116+ const [isSwitchingRuntimeMode, setIsSwitchingRuntimeMode] = useState(false);
116117 const [respondingRequestIds, setRespondingRequestIds] = useState<string[]>(
117118 [],
118119 );
@@ -210,6 +211,35 @@ export default function ChatView() { · 91 unmodified lines
210211 sandboxMode: "workspace-write",
211212 } as const);
212213
214+ const handleRuntimeModeChange = async (
215+ mode: "approval-required" | "full-access",
216+ ) => {
217+ if (mode === state.runtimeMode) return;
218+ dispatch({ type: "SET_RUNTIME_MODE", mode });
219+ if (!api) return;
220+
221+ const sessionIds = state.threads
222+ .map((t) => t.session)
223+ .filter(
224+ (s): s is NonNullable<typeof s> =>
225+ s !== null && s.status !== "closed",
226+ )
227+ .map((s) => s.sessionId);
228+
229+ if (sessionIds.length === 0) return;
230+
231+ setIsSwitchingRuntimeMode(true);
232+ try {
233+ await Promise.all(
234+ sessionIds.map((id) =>
235+ api.providers.stopSession({ sessionId: id }).catch(() => undefined),
236+ ),
237+ );
238+ } finally {
239+ setIsSwitchingRuntimeMode(false);
240+ }
241+ };
242+
213243 // Auto-scroll on new messages
214244 const messageCount = activeThread?.messages.length ?? 0;
215245 const workLogCount = workLogEntries.length;
@@ -775,6 +805,83 @@ export default function ChatView() { · 559 unmodified lines
775805 />
776806 </svg>
777807 </label>
808+
809+ {/* Divider */}
810+ <div className="mx-0.5 h-4 w-px bg-white/[0.08]" />
811+
812+ {/* Runtime mode toggle */}
813+ <button
814+ type="button"
815+ className="inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-[13px] text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.06] hover:text-[#d0d0d0]"
816+ disabled={isSwitchingRuntimeMode}
817+ onClick={() =>
818+ void handleRuntimeModeChange(
819+ state.runtimeMode === "full-access"
820+ ? "approval-required"
821+ : "full-access",
822+ )
823+ }
824+ title={
825+ state.runtimeMode === "full-access"
826+ ? "Full access — click to require approvals"
827+ : "Approval required — click for full access"
828+ }
829+ >
830+ {state.runtimeMode === "full-access" ? (
831+ <svg
832+ width="14"
833+ height="14"
834+ viewBox="0 0 14 14"
835+ fill="none"
836+ aria-hidden="true"
837+ >
838+ <rect
839+ x="2"
840+ y="5.5"
841+ width="10"
842+ height="7"
843+ rx="1.5"
844+ stroke="currentColor"
845+ strokeWidth="1.3"
846+ />
847+ <path
848+ d="M9.5 5.5V4a2.5 2.5 0 0 0-5 0"
849+ stroke="currentColor"
850+ strokeWidth="1.3"
851+ strokeLinecap="round"
852+ />
853+ </svg>
854+ ) : (
855+ <svg
856+ width="14"
857+ height="14"
858+ viewBox="0 0 14 14"
859+ fill="none"
860+ aria-hidden="true"
861+ >
862+ <rect
863+ x="2"
864+ y="5.5"
865+ width="10"
866+ height="7"
867+ rx="1.5"
868+ stroke="currentColor"
869+ strokeWidth="1.3"
870+ />
871+ <path
872+ d="M4.5 5.5V4a2.5 2.5 0 0 1 5 0v1.5"
873+ stroke="currentColor"
874+ strokeWidth="1.3"
875+ strokeLinecap="round"
876+ />
877+ </svg>
878+ )}
879+ <span>
880+ {state.runtimeMode === "full-access"
881+ ? "Full access"
882+ : "Supervised"}
883+ </span>
884+ </button>
778885 </div>
779886
780887 {/* Right side: send / stop button */}
apps/renderer/src/components/Sidebar.tsx+1−75
@@ -4,6 +4,7 @@ import { readNativeApi } from "../session-logic"; · 3 unmodified lines
44 import { useStore } from "../store";
55 import type { Project } from "../types";
66
7+
78 function formatRelativeTime(iso: string): string {
89 const diff = Date.now() - new Date(iso).getTime();
910 const minutes = Math.floor(diff / 60_000);
@@ -28,7 +29,6 @@ export default function Sidebar() { · 18 unmodified lines
2829 const [addingProject, setAddingProject] = useState(false);
2930 const [newCwd, setNewCwd] = useState("");
3031 const [isPickingFolder, setIsPickingFolder] = useState(false);
31− const [isSwitchingRuntimeMode, setIsSwitchingRuntimeMode] = useState(false);
3232
3333 const handleAddProject = () => {
3434 const cwd = newCwd.trim();
@@ -80,43 +80,6 @@ export default function Sidebar() { · 45 unmodified lines
8080 }
8181 };
8282
83− const handleRuntimeModeChange = useCallback(
84− async (mode: "approval-required" | "full-access") => {
85− if (mode === state.runtimeMode) return;
86−
87− dispatch({
88− type: "SET_RUNTIME_MODE",
89− mode,
90− });
91−
92− if (!api) return;
93−
94− const sessionIds = state.threads
95− .map((thread) => thread.session)
96− .filter(
97− (session): session is NonNullable<typeof session> =>
98− session !== null && session.status !== "closed",
99− )
100− .map((session) => session.sessionId);
101−
102− if (sessionIds.length === 0) {
103− return;
104− }
105−
106− setIsSwitchingRuntimeMode(true);
107− try {
108− await Promise.all(
109− sessionIds.map((sessionId) =>
110− api.providers.stopSession({ sessionId }).catch(() => undefined),
111− ),
112− );
113− } finally {
114− setIsSwitchingRuntimeMode(false);
115− }
116− },
117− [api, dispatch, state.runtimeMode, state.threads],
118− );
119−
12083 useEffect(() => {
12184 const onWindowKeyDown = (event: KeyboardEvent) => {
12285 const isNewThreadShortcut =
@@ -189,43 +152,6 @@ export default function Sidebar() { · 66 unmodified lines
189152 </button>
190153 </div>
191154
192− <div className="px-3 pb-3">
193− <div className="rounded-lg border border-white/[0.08] bg-white/[0.03] p-2">
194− <p className="px-1 text-[10px] font-medium uppercase tracking-[0.08em] text-[#a0a0a0]/60">
195− Runtime mode
196− </p>
197− <p className="px-1 pt-0.5 pb-2 text-[10px] text-[#a0a0a0]/45">
198− Switching restarts live sessions.
199− </p>
200− <div className="grid grid-cols-2 gap-1">
201− <button
202− type="button"
203− className={`rounded-md px-2 py-1.5 text-[11px] transition-colors duration-150 ${
204− state.runtimeMode === "approval-required"
205− ? "bg-white/12 text-white"
206− : "text-[#a0a0a0]/70 hover:bg-white/[0.06]"
207− }`}
208− disabled={isSwitchingRuntimeMode}
209− onClick={() => void handleRuntimeModeChange("approval-required")}
210− >
211− Approval required
212− </button>
213− <button
214− type="button"
215− className={`rounded-md px-2 py-1.5 text-[11px] transition-colors duration-150 ${
216− state.runtimeMode === "full-access"
217− ? "bg-white/12 text-white"
218− : "text-[#a0a0a0]/70 hover:bg-white/[0.06]"
219− }`}
220− disabled={isSwitchingRuntimeMode}
221− onClick={() => void handleRuntimeModeChange("full-access")}
222− >
223− Full access
224− </button>
225− </div>
226− </div>
227− </div>
228−
229155 {/* Project list */}
230156 <nav className="flex-1 overflow-y-auto px-2">
231157 {state.projects.map((project) => {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t