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
+1
View File
@@ -332,6 +332,7 @@ export interface SettingsPayload {
showReasoningTraces?: boolean;
autoDeleteEnabled?: boolean;
autoDeleteAfterDays?: number;
queueModeEnabled?: boolean;
[key: string]: unknown;
}
+1
View File
@@ -43,6 +43,7 @@ export type DesktopSettings = {
autoDeleteAfterDays?: number;
defaultModel?: string; // format: "provider/model"
defaultAgent?: string;
queueModeEnabled?: boolean;
};
export type DesktopSettingsApi = {
+32
View File
@@ -393,6 +393,16 @@ class OpencodeService {
filename?: string;
url: string;
}>;
/** Additional text/file parts to include (for batch sending queued messages) */
additionalParts?: Array<{
text: string;
files?: Array<{
type: 'file';
mime: string;
filename?: string;
url: string;
}>;
}>;
messageId?: string;
agentMentions?: Array<{ name: string; source?: { value: string; start: number; end: number } }>;
}): Promise<string> {
@@ -433,6 +443,28 @@ class OpencodeService {
});
}
// Add additional parts (for batch/queued messages)
if (params.additionalParts && params.additionalParts.length > 0) {
for (const additional of params.additionalParts) {
if (additional.text && additional.text.trim()) {
parts.push({
type: 'text',
text: additional.text
});
}
if (additional.files && additional.files.length > 0) {
for (const file of additional.files) {
parts.push({
type: 'file',
mime: file.mime,
filename: file.filename,
url: file.url
});
}
}
}
}
if (params.agentMentions && params.agentMentions.length > 0) {
const [first] = params.agentMentions;
if (first?.name) {
+8
View File
@@ -1,6 +1,7 @@
import { getDesktopSettings, updateDesktopSettings as updateDesktopSettingsApi, isDesktopRuntime } from '@/lib/desktop';
import type { DesktopSettings } from '@/lib/desktop';
import { useUIStore } from '@/stores/useUIStore';
import { useMessageQueueStore } from '@/stores/messageQueueStore';
import { loadAppearancePreferences, applyAppearancePreferences } from '@/lib/appearancePersistence';
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
@@ -55,6 +56,7 @@ const getRuntimeSettingsAPI = () => getRegisteredRuntimeAPIs()?.settings ?? null
const applyDesktopUiPreferences = (settings: DesktopSettings) => {
const store = useUIStore.getState();
const queueStore = useMessageQueueStore.getState();
if (typeof settings.showReasoningTraces === 'boolean' && settings.showReasoningTraces !== store.showReasoningTraces) {
store.setShowReasoningTraces(settings.showReasoningTraces);
@@ -68,6 +70,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
store.setAutoDeleteAfterDays(normalized);
}
}
if (typeof settings.queueModeEnabled === 'boolean' && settings.queueModeEnabled !== queueStore.queueModeEnabled) {
queueStore.setQueueMode(settings.queueModeEnabled);
}
};
const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
@@ -131,6 +136,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
if (typeof candidate.defaultAgent === 'string' && candidate.defaultAgent.length > 0) {
result.defaultAgent = candidate.defaultAgent;
}
if (typeof candidate.queueModeEnabled === 'boolean') {
result.queueModeEnabled = candidate.queueModeEnabled;
}
return result;
};