Unverified 提交 66e37c3e authored 作者: Mohamed Aziz Mejri's avatar Mohamed Aziz Mejri 提交者: GitHub

Adding notification for planning questionnaire (#3028)

closes #2808 --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 4fb6701b
......@@ -9,7 +9,7 @@ const testWithNotificationsEnabled = testWithConfig({
fs.mkdirSync(userDataDir, { recursive: true });
fs.writeFileSync(
path.join(userDataDir, "user-settings.json"),
JSON.stringify({ enableChatCompletionNotifications: true }, null, 2),
JSON.stringify({ enableChatEventNotifications: true }, null, 2),
);
},
});
......@@ -21,9 +21,7 @@ test("notification banner - skip hides permanently", async ({ po }) => {
// Banner should be visible since notifications are not enabled
const banner = po.page.getByTestId("notification-tip-banner");
await expect(banner).toBeVisible();
await expect(banner).toContainText(
"Get notified when chat responses finish.",
);
await expect(banner).toContainText("Get notified about chat events.");
// Record settings before skipping
const beforeSettings = po.settings.recordSettings();
......@@ -69,7 +67,7 @@ test("notification banner - Enable enables notifications and hides banner", asyn
// Banner should be hidden after enabling
await expect(banner).not.toBeVisible();
// Verify settings were updated with enableChatCompletionNotifications: true
// Verify settings were updated with enableChatEventNotifications: true
po.settings.snapshotSettingsDelta(beforeSettings);
// Navigate away and back to verify banner stays hidden
......
+ "enableChatCompletionNotifications": true
\ No newline at end of file
+ "enableChatEventNotifications": true
\ No newline at end of file
......@@ -3,7 +3,7 @@ import { Switch } from "@/components/ui/switch";
import { MacNotificationGuideDialog } from "./MacNotificationGuideDialog";
import { useEnableNotifications } from "@/hooks/useEnableNotifications";
export function ChatCompletionNotificationSwitch() {
export function ChatEventNotificationSwitch() {
const { isEnabled, enable, disable, showMacGuide, setShowMacGuide } =
useEnableNotifications();
......@@ -11,7 +11,7 @@ export function ChatCompletionNotificationSwitch() {
<>
<div className="flex items-center space-x-2">
<Switch
id="chat-completion-notifications"
id="chat-event-notifications"
checked={isEnabled}
onCheckedChange={async (checked) => {
if (checked) {
......@@ -21,9 +21,7 @@ export function ChatCompletionNotificationSwitch() {
}
}}
/>
<Label htmlFor="chat-completion-notifications">
Show notification when chat completes
</Label>
<Label htmlFor="chat-event-notifications">Enable notifications</Label>
</div>
<MacNotificationGuideDialog
open={showMacGuide}
......
......@@ -10,7 +10,7 @@ export function NotificationBanner() {
const showBanner =
settings &&
settings.enableChatCompletionNotifications !== true &&
settings.enableChatEventNotifications !== true &&
settings.skipNotificationBanner !== true;
const handleSkip = () => {
......@@ -22,7 +22,7 @@ export function NotificationBanner() {
{showBanner && (
<SkippableBanner
icon={Bell}
message="Get notified when chat responses finish."
message="Get notified about chat events."
enableLabel="Enable"
onEnable={enable}
onSkip={handleSkip}
......
......@@ -5,7 +5,7 @@ import { detectIsMac } from "@/hooks/useChatModeToggle";
function sendTestNotification() {
if (Notification.permission === "granted") {
new Notification("Dyad", {
body: "Notifications are working! You'll be notified when chat responses complete.",
body: "Notifications are working! You'll be notified when responses finish or input is needed.",
});
}
}
......@@ -13,7 +13,7 @@ function sendTestNotification() {
export function useEnableNotifications() {
const { settings, updateSettings } = useSettings();
const [showMacGuide, setShowMacGuide] = useState(false);
const isEnabled = settings?.enableChatCompletionNotifications === true;
const isEnabled = settings?.enableChatEventNotifications === true;
const isMac = detectIsMac();
const openMacGuide = useCallback(() => {
if (isMac) {
......@@ -33,13 +33,13 @@ export function useEnableNotifications() {
return;
}
}
await updateSettings({ enableChatCompletionNotifications: true });
await updateSettings({ enableChatEventNotifications: true });
sendTestNotification();
openMacGuide();
}, [updateSettings, openMacGuide]);
const disable = useCallback(async () => {
await updateSettings({ enableChatCompletionNotifications: false });
await updateSettings({ enableChatEventNotifications: false });
}, [updateSettings]);
return { isEnabled, enable, disable, showMacGuide, setShowMacGuide };
......
......@@ -18,7 +18,7 @@ import {
type PlanExitPayload,
type PlanQuestionnairePayload,
} from "@/ipc/types/plan";
import { ipc } from "@/ipc/types";
import { ipc, type App } from "@/ipc/types";
import { showError } from "@/lib/toast";
/**
......@@ -177,6 +177,22 @@ export function usePlanEvents() {
next.set(payload.chatId, payload);
return next;
});
// Show native notification if enabled and window is not focused
const notificationsEnabled =
settingsRef.current?.enableChatEventNotifications === true;
if (
notificationsEnabled &&
Notification.permission === "granted" &&
!document.hasFocus()
) {
const app = queryClient.getQueryData<App | null>(
queryKeys.apps.detail({ appId: selectedAppIdRef.current! }),
);
new Notification(app?.name ?? "Dyad", {
body: "A questionnaire needs your input",
});
}
},
);
......
......@@ -229,7 +229,7 @@ export function useStreamChat({
// Show native notification if enabled and window is not focused
// Fire-and-forget to avoid blocking UI updates
const notificationsEnabled =
settings?.enableChatCompletionNotifications === true;
settings?.enableChatEventNotifications === true;
if (
notificationsEnabled &&
Notification.permission === "granted" &&
......
......@@ -331,7 +331,7 @@ const BaseUserSettingsFields = {
enableAutoFixProblems: z.boolean().optional(),
autoExpandPreviewPanel: z.boolean().optional(),
enableChatCompletionNotifications: z.boolean().optional(),
enableChatEventNotifications: z.boolean().optional(),
enableNativeGit: z.boolean().optional(),
enableMcpServersForBuildMode: z.boolean().optional(),
enableAutoUpdate: z.boolean(),
......@@ -365,6 +365,8 @@ export const StoredUserSettingsSchema = z
// Use StoredChatModeSchema to allow deprecated "agent" value
selectedChatMode: StoredChatModeSchema.optional(),
defaultChatMode: StoredChatModeSchema.optional(),
// Deprecated: renamed to enableChatEventNotifications
enableChatCompletionNotifications: z.boolean().optional(),
})
// Allow unknown properties to pass through (e.g. future settings
// that should be preserved if user downgrades to an older version)
......@@ -419,6 +421,9 @@ export function migrateStoredSettings(
...stored,
selectedChatMode: migrateStoredChatMode(stored.selectedChatMode),
defaultChatMode: migrateStoredChatMode(stored.defaultChatMode),
enableChatEventNotifications:
stored.enableChatEventNotifications ??
stored.enableChatCompletionNotifications,
};
}
......
......@@ -22,7 +22,7 @@ export const SETTING_IDS = {
autoApprove: "setting-auto-approve",
autoFix: "setting-auto-fix",
autoExpandPreview: "setting-auto-expand-preview",
chatCompletionNotification: "setting-chat-completion-notification",
chatEventNotification: "setting-chat-event-notification",
thinkingBudget: "setting-thinking-budget",
maxChatTurns: "setting-max-chat-turns",
maxToolCallSteps: "setting-max-tool-call-steps",
......@@ -134,11 +134,18 @@ export const SETTINGS_SEARCH_INDEX: SearchableSettingItem[] = [
sectionLabel: "Workflow",
},
{
id: SETTING_IDS.chatCompletionNotification,
label: "Chat Completion Notification",
id: SETTING_IDS.chatEventNotification,
label: "Notifications",
description:
"Show a native notification when a chat response completes while the app is not focused",
keywords: ["notification", "chat", "complete", "alert", "background"],
"Show native notifications when a chat response completes or a questionnaire needs your input while the app is not focused",
keywords: [
"notification",
"chat",
"complete",
"questionnaire",
"alert",
"background",
],
sectionId: SECTION_IDS.workflow,
sectionLabel: "Workflow",
},
......
......@@ -22,7 +22,7 @@ import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch";
import { AutoExpandPreviewSwitch } from "@/components/AutoExpandPreviewSwitch";
import { ChatCompletionNotificationSwitch } from "@/components/ChatCompletionNotificationSwitch";
import { ChatEventNotificationSwitch } from "@/components/ChatEventNotificationSwitch";
import { AutoUpdateSwitch } from "@/components/AutoUpdateSwitch";
import { ReleaseChannelSelector } from "@/components/ReleaseChannelSelector";
import { NeonIntegration } from "@/components/NeonIntegration";
......@@ -408,14 +408,11 @@ export function WorkflowSettings() {
</div>
</div>
<div
id={SETTING_IDS.chatCompletionNotification}
className="space-y-1 mt-4"
>
<ChatCompletionNotificationSwitch />
<div id={SETTING_IDS.chatEventNotification} className="space-y-1 mt-4">
<ChatEventNotificationSwitch />
<div className="text-sm text-gray-500 dark:text-gray-400">
Show a native notification when a chat response completes while the
app is not focused.
Show native notifications when a chat response completes or a
questionnaire needs your input while the app is not focused.
</div>
</div>
</div>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论