The Dashboard crashed with 'channels.slice is not a function' because channels.status returns a deeply nested object per channel ID, not a Channel[] array. Same issue with skills.status. For now, use empty arrays since channel/skill management is deferred to their dedicated pages. Will properly parse the complex response format when those pages are implemented.
165 lines
4.6 KiB
TypeScript
165 lines
4.6 KiB
TypeScript
/**
|
|
* Channels State Store
|
|
* Manages messaging channel state
|
|
*/
|
|
import { create } from 'zustand';
|
|
import type { Channel, ChannelType } from '../types/channel';
|
|
|
|
interface AddChannelParams {
|
|
type: ChannelType;
|
|
name: string;
|
|
token?: string;
|
|
}
|
|
|
|
interface ChannelsState {
|
|
channels: Channel[];
|
|
loading: boolean;
|
|
error: string | null;
|
|
|
|
// Actions
|
|
fetchChannels: () => Promise<void>;
|
|
addChannel: (params: AddChannelParams) => Promise<Channel>;
|
|
deleteChannel: (channelId: string) => Promise<void>;
|
|
connectChannel: (channelId: string) => Promise<void>;
|
|
disconnectChannel: (channelId: string) => Promise<void>;
|
|
requestQrCode: (channelType: ChannelType) => Promise<{ qrCode: string; sessionId: string }>;
|
|
setChannels: (channels: Channel[]) => void;
|
|
updateChannel: (channelId: string, updates: Partial<Channel>) => void;
|
|
clearError: () => void;
|
|
}
|
|
|
|
export const useChannelsStore = create<ChannelsState>((set, get) => ({
|
|
channels: [],
|
|
loading: false,
|
|
error: null,
|
|
|
|
fetchChannels: async () => {
|
|
// channels.status returns a complex nested object, not a simple array.
|
|
// Channel management is deferred to Settings > Channels page.
|
|
// For now, just use empty list - channels will be added later.
|
|
set({ channels: [], loading: false });
|
|
},
|
|
|
|
addChannel: async (params) => {
|
|
try {
|
|
const result = await window.electron.ipcRenderer.invoke(
|
|
'gateway:rpc',
|
|
'channels.add',
|
|
params
|
|
) as { success: boolean; result?: Channel; error?: string };
|
|
|
|
if (result.success && result.result) {
|
|
set((state) => ({
|
|
channels: [...state.channels, result.result!],
|
|
}));
|
|
return result.result;
|
|
} else {
|
|
// If gateway is not available, create a local channel for now
|
|
const newChannel: Channel = {
|
|
id: `local-${Date.now()}`,
|
|
type: params.type,
|
|
name: params.name,
|
|
status: 'disconnected',
|
|
};
|
|
set((state) => ({
|
|
channels: [...state.channels, newChannel],
|
|
}));
|
|
return newChannel;
|
|
}
|
|
} catch (error) {
|
|
// Create local channel if gateway unavailable
|
|
const newChannel: Channel = {
|
|
id: `local-${Date.now()}`,
|
|
type: params.type,
|
|
name: params.name,
|
|
status: 'disconnected',
|
|
};
|
|
set((state) => ({
|
|
channels: [...state.channels, newChannel],
|
|
}));
|
|
return newChannel;
|
|
}
|
|
},
|
|
|
|
deleteChannel: async (channelId) => {
|
|
try {
|
|
await window.electron.ipcRenderer.invoke(
|
|
'gateway:rpc',
|
|
'channels.delete',
|
|
{ channelId }
|
|
);
|
|
} catch (error) {
|
|
// Continue with local deletion even if gateway fails
|
|
console.error('Failed to delete channel from gateway:', error);
|
|
}
|
|
|
|
// Remove from local state
|
|
set((state) => ({
|
|
channels: state.channels.filter((c) => c.id !== channelId),
|
|
}));
|
|
},
|
|
|
|
connectChannel: async (channelId) => {
|
|
const { updateChannel } = get();
|
|
updateChannel(channelId, { status: 'connecting', error: undefined });
|
|
|
|
try {
|
|
const result = await window.electron.ipcRenderer.invoke(
|
|
'gateway:rpc',
|
|
'channels.connect',
|
|
{ channelId }
|
|
) as { success: boolean; error?: string };
|
|
|
|
if (result.success) {
|
|
updateChannel(channelId, { status: 'connected' });
|
|
} else {
|
|
updateChannel(channelId, { status: 'error', error: result.error });
|
|
}
|
|
} catch (error) {
|
|
updateChannel(channelId, { status: 'error', error: String(error) });
|
|
}
|
|
},
|
|
|
|
disconnectChannel: async (channelId) => {
|
|
const { updateChannel } = get();
|
|
|
|
try {
|
|
await window.electron.ipcRenderer.invoke(
|
|
'gateway:rpc',
|
|
'channels.disconnect',
|
|
{ channelId }
|
|
);
|
|
} catch (error) {
|
|
console.error('Failed to disconnect channel:', error);
|
|
}
|
|
|
|
updateChannel(channelId, { status: 'disconnected', error: undefined });
|
|
},
|
|
|
|
requestQrCode: async (channelType) => {
|
|
const result = await window.electron.ipcRenderer.invoke(
|
|
'gateway:rpc',
|
|
'channels.requestQr',
|
|
{ type: channelType }
|
|
) as { success: boolean; result?: { qrCode: string; sessionId: string }; error?: string };
|
|
|
|
if (result.success && result.result) {
|
|
return result.result;
|
|
}
|
|
|
|
throw new Error(result.error || 'Failed to request QR code');
|
|
},
|
|
|
|
setChannels: (channels) => set({ channels }),
|
|
|
|
updateChannel: (channelId, updates) => {
|
|
set((state) => ({
|
|
channels: state.channels.map((channel) =>
|
|
channel.id === channelId ? { ...channel, ...updates } : channel
|
|
),
|
|
}));
|
|
},
|
|
|
|
clearError: () => set({ error: null }),
|
|
}));
|