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