feat: Improve notifications with templates, summarization, and more (#317)
* feat: Successfully added new notification settings to Zustand * feat: Successfully implemented the Events subsection * feat: Successfully wired new notification settings * feat: Added notification template editor section to Notifications * feat: Added server-side template resolution and summarize * feat: Improve notifications with templates, summarization, and more * fix: session not populated in messages * fix notification template first-run defaults --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
4194cd25d6
commit
fbf0ec50ee
@@ -8,6 +8,19 @@ type AppearanceSlice = {
|
||||
nativeNotificationsEnabled: boolean;
|
||||
notificationMode: 'always' | 'hidden-only';
|
||||
notifyOnSubtasks: boolean;
|
||||
notifyOnCompletion: boolean;
|
||||
notifyOnError: boolean;
|
||||
notifyOnQuestion: boolean;
|
||||
notificationTemplates: {
|
||||
completion: { title: string; message: string };
|
||||
error: { title: string; message: string };
|
||||
question: { title: string; message: string };
|
||||
subtask: { title: string; message: string };
|
||||
};
|
||||
summarizeLastMessage: boolean;
|
||||
summaryThreshold: number;
|
||||
summaryLength: number;
|
||||
maxLastMessageLength: number;
|
||||
autoDeleteEnabled: boolean;
|
||||
autoDeleteAfterDays: number;
|
||||
toolCallExpansion: 'collapsed' | 'activity' | 'detailed';
|
||||
@@ -35,6 +48,14 @@ export const startAppearanceAutoSave = (): void => {
|
||||
nativeNotificationsEnabled: useUIStore.getState().nativeNotificationsEnabled,
|
||||
notificationMode: useUIStore.getState().notificationMode,
|
||||
notifyOnSubtasks: useUIStore.getState().notifyOnSubtasks,
|
||||
notifyOnCompletion: useUIStore.getState().notifyOnCompletion,
|
||||
notifyOnError: useUIStore.getState().notifyOnError,
|
||||
notifyOnQuestion: useUIStore.getState().notifyOnQuestion,
|
||||
notificationTemplates: useUIStore.getState().notificationTemplates,
|
||||
summarizeLastMessage: useUIStore.getState().summarizeLastMessage,
|
||||
summaryThreshold: useUIStore.getState().summaryThreshold,
|
||||
summaryLength: useUIStore.getState().summaryLength,
|
||||
maxLastMessageLength: useUIStore.getState().maxLastMessageLength,
|
||||
autoDeleteEnabled: useUIStore.getState().autoDeleteEnabled,
|
||||
autoDeleteAfterDays: useUIStore.getState().autoDeleteAfterDays,
|
||||
toolCallExpansion: useUIStore.getState().toolCallExpansion,
|
||||
@@ -74,6 +95,14 @@ export const startAppearanceAutoSave = (): void => {
|
||||
nativeNotificationsEnabled: state.nativeNotificationsEnabled,
|
||||
notificationMode: state.notificationMode,
|
||||
notifyOnSubtasks: state.notifyOnSubtasks,
|
||||
notifyOnCompletion: state.notifyOnCompletion,
|
||||
notifyOnError: state.notifyOnError,
|
||||
notifyOnQuestion: state.notifyOnQuestion,
|
||||
notificationTemplates: state.notificationTemplates,
|
||||
summarizeLastMessage: state.summarizeLastMessage,
|
||||
summaryThreshold: state.summaryThreshold,
|
||||
summaryLength: state.summaryLength,
|
||||
maxLastMessageLength: state.maxLastMessageLength,
|
||||
autoDeleteEnabled: state.autoDeleteEnabled,
|
||||
autoDeleteAfterDays: state.autoDeleteAfterDays,
|
||||
toolCallExpansion: state.toolCallExpansion,
|
||||
@@ -103,6 +132,30 @@ export const startAppearanceAutoSave = (): void => {
|
||||
if (current.notifyOnSubtasks !== previous.notifyOnSubtasks) {
|
||||
diff.notifyOnSubtasks = current.notifyOnSubtasks;
|
||||
}
|
||||
if (current.notifyOnCompletion !== previous.notifyOnCompletion) {
|
||||
diff.notifyOnCompletion = current.notifyOnCompletion;
|
||||
}
|
||||
if (current.notifyOnError !== previous.notifyOnError) {
|
||||
diff.notifyOnError = current.notifyOnError;
|
||||
}
|
||||
if (current.notifyOnQuestion !== previous.notifyOnQuestion) {
|
||||
diff.notifyOnQuestion = current.notifyOnQuestion;
|
||||
}
|
||||
if (JSON.stringify(current.notificationTemplates) !== JSON.stringify(previous.notificationTemplates)) {
|
||||
diff.notificationTemplates = current.notificationTemplates;
|
||||
}
|
||||
if (current.summarizeLastMessage !== previous.summarizeLastMessage) {
|
||||
diff.summarizeLastMessage = current.summarizeLastMessage;
|
||||
}
|
||||
if (current.summaryThreshold !== previous.summaryThreshold) {
|
||||
diff.summaryThreshold = current.summaryThreshold;
|
||||
}
|
||||
if (current.summaryLength !== previous.summaryLength) {
|
||||
diff.summaryLength = current.summaryLength;
|
||||
}
|
||||
if (current.maxLastMessageLength !== previous.maxLastMessageLength) {
|
||||
diff.maxLastMessageLength = current.maxLastMessageLength;
|
||||
}
|
||||
if (current.autoDeleteEnabled !== previous.autoDeleteEnabled) {
|
||||
diff.autoDeleteEnabled = current.autoDeleteEnabled;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,26 @@ export type DesktopSettings = {
|
||||
nativeNotificationsEnabled?: boolean;
|
||||
notificationMode?: 'always' | 'hidden-only';
|
||||
notifyOnSubtasks?: boolean;
|
||||
|
||||
// Event toggles (which events trigger notifications)
|
||||
notifyOnCompletion?: boolean;
|
||||
notifyOnError?: boolean;
|
||||
notifyOnQuestion?: boolean;
|
||||
|
||||
// Per-event notification templates
|
||||
notificationTemplates?: {
|
||||
completion: { title: string; message: string };
|
||||
error: { title: string; message: string };
|
||||
question: { title: string; message: string };
|
||||
subtask: { title: string; message: string };
|
||||
};
|
||||
|
||||
// Summarization settings
|
||||
summarizeLastMessage?: boolean;
|
||||
summaryThreshold?: number;
|
||||
summaryLength?: number;
|
||||
maxLastMessageLength?: number;
|
||||
|
||||
usageAutoRefresh?: boolean;
|
||||
usageRefreshIntervalMs?: number;
|
||||
usageDisplayMode?: 'usage' | 'remaining';
|
||||
|
||||
@@ -245,6 +245,30 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
||||
if (typeof settings.notifyOnSubtasks === 'boolean' && settings.notifyOnSubtasks !== store.notifyOnSubtasks) {
|
||||
store.setNotifyOnSubtasks(settings.notifyOnSubtasks);
|
||||
}
|
||||
if (typeof settings.notifyOnCompletion === 'boolean' && settings.notifyOnCompletion !== store.notifyOnCompletion) {
|
||||
store.setNotifyOnCompletion(settings.notifyOnCompletion);
|
||||
}
|
||||
if (typeof settings.notifyOnError === 'boolean' && settings.notifyOnError !== store.notifyOnError) {
|
||||
store.setNotifyOnError(settings.notifyOnError);
|
||||
}
|
||||
if (typeof settings.notifyOnQuestion === 'boolean' && settings.notifyOnQuestion !== store.notifyOnQuestion) {
|
||||
store.setNotifyOnQuestion(settings.notifyOnQuestion);
|
||||
}
|
||||
if (settings.notificationTemplates && typeof settings.notificationTemplates === 'object') {
|
||||
store.setNotificationTemplates(settings.notificationTemplates);
|
||||
}
|
||||
if (typeof settings.summarizeLastMessage === 'boolean' && settings.summarizeLastMessage !== store.summarizeLastMessage) {
|
||||
store.setSummarizeLastMessage(settings.summarizeLastMessage);
|
||||
}
|
||||
if (typeof settings.summaryThreshold === 'number' && Number.isFinite(settings.summaryThreshold)) {
|
||||
store.setSummaryThreshold(settings.summaryThreshold);
|
||||
}
|
||||
if (typeof settings.summaryLength === 'number' && Number.isFinite(settings.summaryLength)) {
|
||||
store.setSummaryLength(settings.summaryLength);
|
||||
}
|
||||
if (typeof settings.maxLastMessageLength === 'number' && Number.isFinite(settings.maxLastMessageLength)) {
|
||||
store.setMaxLastMessageLength(settings.maxLastMessageLength);
|
||||
}
|
||||
if (typeof settings.toolCallExpansion === 'string'
|
||||
&& (settings.toolCallExpansion === 'collapsed' || settings.toolCallExpansion === 'activity' || settings.toolCallExpansion === 'detailed')) {
|
||||
if (settings.toolCallExpansion !== store.toolCallExpansion) {
|
||||
@@ -407,6 +431,50 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.notifyOnSubtasks === 'boolean') {
|
||||
result.notifyOnSubtasks = candidate.notifyOnSubtasks;
|
||||
}
|
||||
if (typeof candidate.notifyOnCompletion === 'boolean') {
|
||||
result.notifyOnCompletion = candidate.notifyOnCompletion;
|
||||
}
|
||||
if (typeof candidate.notifyOnError === 'boolean') {
|
||||
result.notifyOnError = candidate.notifyOnError;
|
||||
}
|
||||
if (typeof candidate.notifyOnQuestion === 'boolean') {
|
||||
result.notifyOnQuestion = candidate.notifyOnQuestion;
|
||||
}
|
||||
if (candidate.notificationTemplates && typeof candidate.notificationTemplates === 'object') {
|
||||
const templates = candidate.notificationTemplates as Record<string, unknown>;
|
||||
const validateTemplate = (key: string): { title: string; message: string } | undefined => {
|
||||
const value = templates[key];
|
||||
if (!value || typeof value !== 'object') return undefined;
|
||||
const obj = value as Record<string, unknown>;
|
||||
const title = typeof obj.title === 'string' ? obj.title : '';
|
||||
const message = typeof obj.message === 'string' ? obj.message : '';
|
||||
return { title, message };
|
||||
};
|
||||
const completion = validateTemplate('completion');
|
||||
const error = validateTemplate('error');
|
||||
const question = validateTemplate('question');
|
||||
const subtask = validateTemplate('subtask');
|
||||
if (completion || error || question || subtask) {
|
||||
result.notificationTemplates = {
|
||||
completion: completion ?? { title: 'Task Complete', message: 'Your task has finished.' },
|
||||
error: error ?? { title: 'Error Occurred', message: 'An error occurred while processing your task.' },
|
||||
question: question ?? { title: 'Input Needed', message: 'Please provide input to continue.' },
|
||||
subtask: subtask ?? { title: 'Subtask Complete', message: 'A subtask has finished.' },
|
||||
};
|
||||
}
|
||||
}
|
||||
if (typeof candidate.summarizeLastMessage === 'boolean') {
|
||||
result.summarizeLastMessage = candidate.summarizeLastMessage;
|
||||
}
|
||||
if (typeof candidate.summaryThreshold === 'number' && Number.isFinite(candidate.summaryThreshold)) {
|
||||
result.summaryThreshold = Math.max(0, Math.round(candidate.summaryThreshold));
|
||||
}
|
||||
if (typeof candidate.summaryLength === 'number' && Number.isFinite(candidate.summaryLength)) {
|
||||
result.summaryLength = Math.max(10, Math.round(candidate.summaryLength));
|
||||
}
|
||||
if (typeof candidate.maxLastMessageLength === 'number' && Number.isFinite(candidate.maxLastMessageLength)) {
|
||||
result.maxLastMessageLength = Math.max(10, Math.round(candidate.maxLastMessageLength));
|
||||
}
|
||||
if (typeof candidate.usageAutoRefresh === 'boolean') {
|
||||
result.usageAutoRefresh = candidate.usageAutoRefresh;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user