feat: add message queue functionality with auto-send and queue mode toggle

- Improved development server hot module replacement (HMR) by reusing OpenCode processes.
- Introduced queued message mode with batching and idle auto-send, including support for attachments.
- Added a toggle for queue mode in OpenChamber settings with persistence across sessions.
- Updated various components to handle queued messages, including ChatInput and QueuedMessageChips.
- Modified settings payload and desktop settings to include queue mode configuration.
- Enhanced message sending logic to support additional parts for queued messages.
This commit is contained in:
Bohdan Triapitsyn
2025-12-29 02:16:42 +02:00
parent aefbb1eac2
commit 66428cbded
15 changed files with 668 additions and 59 deletions
+144
View File
@@ -0,0 +1,144 @@
import { create } from 'zustand';
import { devtools, persist, createJSONStorage } from 'zustand/middleware';
import { getSafeStorage } from './utils/safeStorage';
import type { AttachedFile } from './types/sessionTypes';
import { updateDesktopSettings } from '@/lib/persistence';
export interface QueuedMessage {
id: string;
content: string;
attachments?: AttachedFile[];
createdAt: number;
}
interface MessageQueueState {
queuedMessages: Record<string, QueuedMessage[]>; // sessionId → queue
queueModeEnabled: boolean; // global toggle
}
interface MessageQueueActions {
addToQueue: (sessionId: string, message: Omit<QueuedMessage, 'id' | 'createdAt'>) => void;
removeFromQueue: (sessionId: string, messageId: string) => void;
popToInput: (sessionId: string, messageId: string) => QueuedMessage | null;
clearQueue: (sessionId: string) => void;
clearAllQueues: () => void;
setQueueMode: (enabled: boolean) => void;
getQueueForSession: (sessionId: string) => QueuedMessage[];
}
type MessageQueueStore = MessageQueueState & MessageQueueActions;
export const useMessageQueueStore = create<MessageQueueStore>()(
devtools(
persist(
(set, get) => ({
queuedMessages: {},
queueModeEnabled: false,
addToQueue: (sessionId, message) => {
const id = `queued-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const queuedMessage: QueuedMessage = {
id,
content: message.content,
attachments: message.attachments,
createdAt: Date.now(),
};
set((state) => {
const currentQueue = state.queuedMessages[sessionId] ?? [];
return {
queuedMessages: {
...state.queuedMessages,
[sessionId]: [...currentQueue, queuedMessage],
},
};
});
},
removeFromQueue: (sessionId, messageId) => {
set((state) => {
const currentQueue = state.queuedMessages[sessionId] ?? [];
const newQueue = currentQueue.filter((m) => m.id !== messageId);
if (newQueue.length === 0) {
const { [sessionId]: _removed, ...rest } = state.queuedMessages;
void _removed;
return { queuedMessages: rest };
}
return {
queuedMessages: {
...state.queuedMessages,
[sessionId]: newQueue,
},
};
});
},
popToInput: (sessionId, messageId) => {
const state = get();
const currentQueue = state.queuedMessages[sessionId] ?? [];
const message = currentQueue.find((m) => m.id === messageId);
if (!message) {
return null;
}
// Remove from queue
set((prevState) => {
const queue = prevState.queuedMessages[sessionId] ?? [];
const newQueue = queue.filter((m) => m.id !== messageId);
if (newQueue.length === 0) {
const { [sessionId]: _removed, ...rest } = prevState.queuedMessages;
void _removed;
return { queuedMessages: rest };
}
return {
queuedMessages: {
...prevState.queuedMessages,
[sessionId]: newQueue,
},
};
});
return message;
},
clearQueue: (sessionId) => {
set((state) => {
const { [sessionId]: _removed, ...rest } = state.queuedMessages;
void _removed;
return { queuedMessages: rest };
});
},
clearAllQueues: () => {
set({ queuedMessages: {} });
},
setQueueMode: (enabled) => {
set({ queueModeEnabled: enabled });
// Persist to settings.json (async, fire-and-forget)
void updateDesktopSettings({ queueModeEnabled: enabled });
},
getQueueForSession: (sessionId) => {
return get().queuedMessages[sessionId] ?? [];
},
}),
{
name: 'message-queue-store',
storage: createJSONStorage(() => getSafeStorage()),
partialize: (state) => ({
queuedMessages: state.queuedMessages,
queueModeEnabled: state.queueModeEnabled,
}),
}
),
{
name: 'message-queue-store',
}
)
);