Skip to content

Commit 4708e5d

Browse files
feat: approval flow with approve-and-remember, bypass-all buttons, tool name badge
1 parent 538cfa9 commit 4708e5d

1 file changed

Lines changed: 82 additions & 23 deletions

File tree

crates/tauri-app/frontend/src/components/chat/ChatArea.tsx

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import { For, Show, createEffect, createSignal, lazy } from "solid-js";
1+
import { For, Show, createEffect, createSignal } from "solid-js";
22
import { appStore } from "../../stores/app-store";
33
import * as ipc from "../../ipc";
44
import { Markdown } from "./Markdown";
55
import { ToolUseCard } from "./ToolUseCard";
66
import { ThinkingBlock } from "./ThinkingBlock";
77
import { PrDashboard } from "../github/PrDashboard";
8+
import { McpPanel } from "../sidebar/McpPanel";
9+
import { ThemeSelector } from "../settings/ThemeSelector";
810
import type { ContentBlock } from "../../types";
911

10-
const McpPanel = lazy(() => import("../sidebar/McpPanel").then(m => ({ default: m.McpPanel })));
11-
const ThemeSelector = lazy(() => import("../settings/ThemeSelector").then(m => ({ default: m.ThemeSelector })));
12-
1312
export function ChatArea() {
1413
const { store, approveRequest, denyRequest } = appStore;
1514
let scrollRef: HTMLDivElement | undefined;
@@ -265,22 +264,55 @@ export function ChatArea() {
265264
</For>
266265

267266
<For each={store.pendingApprovals.filter((a) => a.threadId === store.activeTab)}>
268-
{(approval) => (
269-
<div class="approval-card">
270-
<div class="approval-header">
271-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--amber)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
272-
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
273-
<line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
274-
</svg>
275-
<span class="approval-title">Permission Required</span>
276-
</div>
277-
<pre class="approval-desc">{approval.description}</pre>
278-
<div class="approval-actions">
279-
<button class="deny-btn" onClick={() => denyRequest(approval)}>Deny</button>
280-
<button class="approve-btn" onClick={() => approveRequest(approval)}>Approve</button>
267+
{(approval) => {
268+
const toolName = () => approval.description.split(":")[0]?.trim() || "tool";
269+
270+
function handleApproveAndRemember() {
271+
// Store an allow rule for this tool
272+
const rule = `${toolName()}`;
273+
ipc.getSetting("allowed_tools").then((current) => {
274+
const rules = current ? current.split(",") : [];
275+
if (!rules.includes(rule)) {
276+
rules.push(rule);
277+
ipc.setSetting("allowed_tools", rules.join(","));
278+
}
279+
}).catch(() => {});
280+
approveRequest(approval);
281+
}
282+
283+
function handleBypassSession() {
284+
// Approve this request and set session to bypass
285+
ipc.setSetting("permission_mode", "bypassPermissions").catch(() => {});
286+
// Approve all pending approvals for this thread
287+
store.pendingApprovals
288+
.filter((a) => a.threadId === approval.threadId)
289+
.forEach((a) => approveRequest(a));
290+
}
291+
292+
return (
293+
<div class="approval-card">
294+
<div class="approval-header">
295+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--amber)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
296+
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
297+
<line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
298+
</svg>
299+
<span class="approval-title">Permission Required</span>
300+
<span class="approval-tool">{toolName()}</span>
301+
</div>
302+
<pre class="approval-desc">{approval.description}</pre>
303+
<div class="approval-actions">
304+
<button class="deny-btn" onClick={() => denyRequest(approval)}>Deny</button>
305+
<button class="approve-btn" onClick={() => approveRequest(approval)}>Approve</button>
306+
<button class="approve-remember-btn" onClick={handleApproveAndRemember} title={`Always allow ${toolName()}`}>
307+
Approve &amp; Remember
308+
</button>
309+
<button class="bypass-btn" onClick={handleBypassSession} title="Auto-approve everything for this session">
310+
Bypass All
311+
</button>
312+
</div>
281313
</div>
282-
</div>
283-
)}
314+
);
315+
}}
284316
</For>
285317

286318
<Show when={isGenerating() && messages().length === 0}>
@@ -868,11 +900,20 @@ if (!document.getElementById("chat-styles")) {
868900
overflow-y: auto;
869901
margin: 0 0 12px;
870902
}
871-
.approval-actions { display: flex; gap: 8px; justify-content: flex-end; }
872-
.approve-btn, .deny-btn {
873-
padding: 7px 16px;
903+
.approval-tool {
904+
font-size: 11px;
905+
font-family: var(--font-mono);
906+
color: var(--text-tertiary);
907+
margin-left: auto;
908+
padding: 1px 6px;
909+
background: var(--bg-accent);
910+
border-radius: var(--radius-pill);
911+
}
912+
.approval-actions { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }
913+
.approve-btn, .deny-btn, .approve-remember-btn, .bypass-btn {
914+
padding: 6px 12px;
874915
border-radius: var(--radius-sm);
875-
font-size: 12px;
916+
font-size: 11px;
876917
font-weight: 600;
877918
transition: all 0.12s;
878919
}
@@ -881,6 +922,24 @@ if (!document.getElementById("chat-styles")) {
881922
color: #fff;
882923
}
883924
.approve-btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
925+
.approve-remember-btn {
926+
background: rgba(76, 214, 148, 0.12);
927+
border: 1px solid rgba(76, 214, 148, 0.25);
928+
color: var(--green);
929+
}
930+
.approve-remember-btn:hover {
931+
background: rgba(76, 214, 148, 0.2);
932+
border-color: rgba(76, 214, 148, 0.4);
933+
}
934+
.bypass-btn {
935+
background: rgba(240, 184, 64, 0.1);
936+
border: 1px solid rgba(240, 184, 64, 0.2);
937+
color: var(--amber);
938+
}
939+
.bypass-btn:hover {
940+
background: rgba(240, 184, 64, 0.18);
941+
border-color: rgba(240, 184, 64, 0.35);
942+
}
884943
.deny-btn {
885944
background: var(--bg-muted);
886945
border: 1px solid var(--border);

0 commit comments

Comments
 (0)