Files
openchamber/packages/ui/src/lib/desktop.ts
T
Bohdan Triapitsyn 66428cbded 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.
2025-12-29 02:16:42 +02:00

299 lines
8.4 KiB
TypeScript

export type AssistantNotificationPayload = {
title?: string;
body?: string;
};
export type UpdateInfo = {
available: boolean;
version?: string;
currentVersion: string;
body?: string;
date?: string;
// Web-specific fields
packageManager?: string;
updateCommand?: string;
};
export type UpdateProgress = {
downloaded: number;
total?: number;
};
export type DesktopServerInfo = {
webPort: number | null;
openCodePort: number | null;
host: string | null;
ready: boolean;
cliAvailable: boolean;
};
export type DesktopSettings = {
themeId?: string;
useSystemTheme?: boolean;
themeVariant?: 'light' | 'dark';
lightThemeId?: string;
darkThemeId?: string;
lastDirectory?: string;
homeDirectory?: string;
approvedDirectories?: string[];
securityScopedBookmarks?: string[];
pinnedDirectories?: string[];
showReasoningTraces?: boolean;
autoDeleteEnabled?: boolean;
autoDeleteAfterDays?: number;
defaultModel?: string; // format: "provider/model"
defaultAgent?: string;
queueModeEnabled?: boolean;
};
export type DesktopSettingsApi = {
getSettings: () => Promise<DesktopSettings>;
updateSettings: (changes: Partial<DesktopSettings>) => Promise<DesktopSettings>;
};
export type DesktopApi = {
homeDirectory?: string;
getServerInfo: () => Promise<DesktopServerInfo>;
restartOpenCode: () => Promise<{ success: boolean }>;
shutdown: () => Promise<{ success: boolean }>;
markRendererReady?: () => Promise<void> | void;
windowControl?: (action: 'close' | 'minimize' | 'maximize') => Promise<{ success: boolean }>;
getHomeDirectory?: () => Promise<{ success: boolean; path: string | null }>;
getSettings?: () => Promise<DesktopSettings>;
updateSettings?: (changes: Partial<DesktopSettings>) => Promise<DesktopSettings>;
requestDirectoryAccess?: (path: string) => Promise<{ success: boolean; path?: string; error?: string }>;
startAccessingDirectory?: (path: string) => Promise<{ success: boolean; error?: string }>;
stopAccessingDirectory?: (path: string) => Promise<{ success: boolean; error?: string }>;
notifyAssistantCompletion?: (payload?: AssistantNotificationPayload) => Promise<{ success: boolean }>;
checkForUpdates?: () => Promise<UpdateInfo>;
downloadUpdate?: (onProgress?: (progress: UpdateProgress) => void) => Promise<void>;
restartToUpdate?: () => Promise<void>;
openExternal?: (url: string) => Promise<{ success: boolean; error?: string }>;
};
export const isDesktopRuntime = (): boolean =>
typeof window !== "undefined" && typeof window.opencodeDesktop !== "undefined";
export const isVSCodeRuntime = (): boolean => {
if (typeof window === "undefined") return false;
const apis = (window as { __OPENCHAMBER_RUNTIME_APIS__?: { runtime?: { isVSCode?: boolean } } }).__OPENCHAMBER_RUNTIME_APIS__;
return apis?.runtime?.isVSCode === true;
};
export const isWebRuntime = (): boolean => {
if (typeof window === "undefined") return false;
// Web runtime: not desktop, not VSCode
return !isDesktopRuntime() && !isVSCodeRuntime();
};
export const getDesktopApi = (): DesktopApi | null => {
if (!isDesktopRuntime()) {
return null;
}
return window.opencodeDesktop ?? null;
};
export const getDesktopSettingsApi = (): DesktopSettingsApi | null => {
if (typeof window === 'undefined') {
return null;
}
if (window.opencodeDesktopSettings) {
return window.opencodeDesktopSettings;
}
const base = window.opencodeDesktop;
if (base?.getSettings && base?.updateSettings) {
return {
getSettings: base.getSettings.bind(base),
updateSettings: base.updateSettings.bind(base)
};
}
return null;
};
export const getDesktopHomeDirectory = async (): Promise<string | null> => {
const api = getDesktopApi();
if (typeof window !== 'undefined') {
const embedded = window.__OPENCHAMBER_HOME__;
if (embedded && embedded.length > 0) {
return embedded;
}
}
if (!api) {
return null;
}
if (typeof api.homeDirectory === 'string' && api.homeDirectory.length > 0) {
return api.homeDirectory;
}
try {
if (!api.getHomeDirectory) {
return null;
}
const result = await api.getHomeDirectory();
if (result?.success && typeof result.path === 'string' && result.path.length > 0) {
return result.path;
}
} catch (error) {
console.warn('Failed to obtain desktop home directory:', error);
}
return null;
};
export const fetchDesktopServerInfo = async (): Promise<DesktopServerInfo | null> => {
const api = getDesktopApi();
if (!api) {
return null;
}
try {
return await api.getServerInfo();
} catch (error) {
console.warn("Failed to read desktop server info", error);
return null;
}
};
export const isCliAvailable = (): boolean => {
if (typeof window === 'undefined') {
return false;
}
return window.__OPENCHAMBER_DESKTOP_SERVER__?.cliAvailable ?? false;
};
export const getDesktopSettings = async (): Promise<DesktopSettings | null> => {
const api = getDesktopSettingsApi();
if (!api) {
return null;
}
try {
return await api.getSettings();
} catch (error) {
console.warn('Failed to read desktop settings', error);
return null;
}
};
export const updateDesktopSettings = async (
changes: Partial<DesktopSettings>
): Promise<DesktopSettings | null> => {
const api = getDesktopSettingsApi();
if (!api) {
return null;
}
try {
return await api.updateSettings(changes);
} catch (error) {
console.warn('[desktop] Failed to update desktop settings', error);
return null;
}
};
export const requestDirectoryAccess = async (
directoryPath: string
): Promise<{ success: boolean; path?: string; error?: string }> => {
const api = getDesktopApi();
if (!api || !api.requestDirectoryAccess) {
return { success: true, path: directoryPath };
}
try {
return await api.requestDirectoryAccess(directoryPath);
} catch (error) {
console.warn('Failed to request directory access', error);
return { success: false, error: error instanceof Error ? error.message : String(error) };
}
};
export const startAccessingDirectory = async (
directoryPath: string
): Promise<{ success: boolean; error?: string }> => {
const api = getDesktopApi();
if (!api || !api.startAccessingDirectory) {
return { success: true };
}
try {
return await api.startAccessingDirectory(directoryPath);
} catch (error) {
console.warn('Failed to start accessing directory', error);
return { success: false, error: error instanceof Error ? error.message : String(error) };
}
};
export const stopAccessingDirectory = async (
directoryPath: string
): Promise<{ success: boolean; error?: string }> => {
const api = getDesktopApi();
if (!api || !api.stopAccessingDirectory) {
return { success: true };
}
try {
return await api.stopAccessingDirectory(directoryPath);
} catch (error) {
console.warn('Failed to stop accessing directory', error);
return { success: false, error: error instanceof Error ? error.message : String(error) };
}
};
export const sendAssistantCompletionNotification = async (
payload?: AssistantNotificationPayload
): Promise<boolean> => {
const api = getDesktopApi();
if (!api || !api.notifyAssistantCompletion) {
return false;
}
try {
const result = await api.notifyAssistantCompletion(payload ?? {});
return Boolean(result?.success);
} catch (error) {
console.warn('Failed to send assistant completion notification', error);
return false;
}
};
export const checkForDesktopUpdates = async (): Promise<UpdateInfo | null> => {
const api = getDesktopApi();
if (!api || !api.checkForUpdates) {
return null;
}
try {
return await api.checkForUpdates();
} catch (error) {
console.warn('Failed to check for updates', error);
return null;
}
};
export const downloadDesktopUpdate = async (
onProgress?: (progress: UpdateProgress) => void
): Promise<boolean> => {
const api = getDesktopApi();
if (!api || !api.downloadUpdate) {
return false;
}
try {
await api.downloadUpdate(onProgress);
return true;
} catch (error) {
console.warn('Failed to download update', error);
return false;
}
};
export const restartToApplyUpdate = async (): Promise<boolean> => {
const api = getDesktopApi();
if (!api || !api.restartToUpdate) {
return false;
}
try {
await api.restartToUpdate();
return true;
} catch (error) {
console.warn('Failed to restart for update', error);
return false;
}
};