feat(provider): mainly support moonshot / siliconflow on setup (#43)

This commit is contained in:
DigHuang
2026-02-10 19:33:33 -08:00
committed by GitHub
Unverified
parent 563fcd2f24
commit 1b508d5bde
16 changed files with 1305 additions and 634 deletions

View File

@@ -283,6 +283,8 @@ export const useChatStore = create<ChatState>((set, get) => ({
set({ error: result.error || 'Failed to send message', sending: false });
} else if (result.result?.runId) {
set({ activeRunId: result.result.runId });
} else {
// No runId from gateway; keep sending state and wait for events.
}
} catch (err) {
set({ error: String(err), sending: false });

View File

@@ -5,6 +5,8 @@
import { create } from 'zustand';
import type { GatewayStatus } from '../types/gateway';
let gatewayInitPromise: Promise<void> | null = null;
interface GatewayHealth {
ok: boolean;
error?: string;
@@ -39,47 +41,79 @@ export const useGatewayStore = create<GatewayState>((set, get) => ({
init: async () => {
if (get().isInitialized) return;
try {
// Get initial status
const status = await window.electron.ipcRenderer.invoke('gateway:status') as GatewayStatus;
set({ status, isInitialized: true });
// Listen for status changes
window.electron.ipcRenderer.on('gateway:status-changed', (newStatus) => {
set({ status: newStatus as GatewayStatus });
});
// Listen for errors
window.electron.ipcRenderer.on('gateway:error', (error) => {
set({ lastError: String(error) });
});
// Listen for notifications
window.electron.ipcRenderer.on('gateway:notification', (notification) => {
console.log('Gateway notification:', notification);
});
// Listen for chat events from the gateway and forward to chat store
window.electron.ipcRenderer.on('gateway:chat-message', (data) => {
try {
// Dynamic import to avoid circular dependency
import('./chat').then(({ useChatStore }) => {
const chatData = data as { message?: Record<string, unknown> } | Record<string, unknown>;
const event = ('message' in chatData && typeof chatData.message === 'object')
? chatData.message as Record<string, unknown>
: chatData as Record<string, unknown>;
useChatStore.getState().handleChatEvent(event);
});
} catch (err) {
console.warn('Failed to forward chat event:', err);
}
});
} catch (error) {
console.error('Failed to initialize Gateway:', error);
set({ lastError: String(error) });
if (gatewayInitPromise) {
await gatewayInitPromise;
return;
}
gatewayInitPromise = (async () => {
try {
// Get initial status first
const status = await window.electron.ipcRenderer.invoke('gateway:status') as GatewayStatus;
set({ status, isInitialized: true });
// Listen for status changes
window.electron.ipcRenderer.on('gateway:status-changed', (newStatus) => {
set({ status: newStatus as GatewayStatus });
});
// Listen for errors
window.electron.ipcRenderer.on('gateway:error', (error) => {
set({ lastError: String(error) });
});
// Some Gateway builds stream chat events via generic "agent" notifications.
// Normalize and forward them to the chat store.
window.electron.ipcRenderer.on('gateway:notification', (notification) => {
const payload = notification as { method?: string; params?: Record<string, unknown> } | undefined;
if (!payload || payload.method !== 'agent' || !payload.params || typeof payload.params !== 'object') {
return;
}
const p = payload.params;
const data = (p.data && typeof p.data === 'object') ? (p.data as Record<string, unknown>) : {};
const normalizedEvent: Record<string, unknown> = {
...data,
runId: p.runId ?? data.runId,
sessionKey: p.sessionKey ?? data.sessionKey,
stream: p.stream ?? data.stream,
seq: p.seq ?? data.seq,
};
import('./chat')
.then(({ useChatStore }) => {
useChatStore.getState().handleChatEvent(normalizedEvent);
})
.catch((err) => {
console.warn('Failed to forward gateway notification event:', err);
});
});
// Listen for chat events from the gateway and forward to chat store
window.electron.ipcRenderer.on('gateway:chat-message', (data) => {
try {
// Dynamic import to avoid circular dependency
import('./chat').then(({ useChatStore }) => {
const chatData = data as { message?: Record<string, unknown> } | Record<string, unknown>;
const event = ('message' in chatData && typeof chatData.message === 'object')
? chatData.message as Record<string, unknown>
: chatData as Record<string, unknown>;
useChatStore.getState().handleChatEvent(event);
});
} catch (err) {
console.warn('Failed to forward chat event:', err);
}
});
} catch (error) {
console.error('Failed to initialize Gateway:', error);
set({ lastError: String(error) });
} finally {
gatewayInitPromise = null;
}
})();
await gatewayInitPromise;
},
start: async () => {

View File

@@ -3,28 +3,10 @@
* Manages AI provider configurations
*/
import { create } from 'zustand';
import type { ProviderConfig, ProviderWithKeyInfo } from '@/lib/providers';
/**
* Provider configuration
*/
export interface ProviderConfig {
id: string;
name: string;
type: 'anthropic' | 'openai' | 'google' | 'openrouter' | 'ollama' | 'custom';
baseUrl?: string;
model?: string;
enabled: boolean;
createdAt: string;
updatedAt: string;
}
/**
* Provider with key info (for display)
*/
export interface ProviderWithKeyInfo extends ProviderConfig {
hasKey: boolean;
keyMasked: string | null;
}
// Re-export types for consumers that imported from here
export type { ProviderConfig, ProviderWithKeyInfo } from '@/lib/providers';
interface ProviderState {
providers: ProviderWithKeyInfo[];
@@ -38,6 +20,11 @@ interface ProviderState {
updateProvider: (providerId: string, updates: Partial<ProviderConfig>, apiKey?: string) => Promise<void>;
deleteProvider: (providerId: string) => Promise<void>;
setApiKey: (providerId: string, apiKey: string) => Promise<void>;
updateProviderWithKey: (
providerId: string,
updates: Partial<ProviderConfig>,
apiKey?: string
) => Promise<void>;
deleteApiKey: (providerId: string) => Promise<void>;
setDefaultProvider: (providerId: string) => Promise<void>;
validateApiKey: (providerId: string, apiKey: string) => Promise<{ valid: boolean; error?: string }>;
@@ -95,9 +82,11 @@ export const useProviderStore = create<ProviderState>((set, get) => ({
if (!existing) {
throw new Error('Provider not found');
}
const { hasKey: _hasKey, keyMasked: _keyMasked, ...providerConfig } = existing;
const updatedConfig: ProviderConfig = {
...existing,
...providerConfig,
...updates,
updatedAt: new Date().toISOString(),
};
@@ -147,6 +136,26 @@ export const useProviderStore = create<ProviderState>((set, get) => ({
throw error;
}
},
updateProviderWithKey: async (providerId, updates, apiKey) => {
try {
const result = await window.electron.ipcRenderer.invoke(
'provider:updateWithKey',
providerId,
updates,
apiKey
) as { success: boolean; error?: string };
if (!result.success) {
throw new Error(result.error || 'Failed to update provider');
}
await get().fetchProviders();
} catch (error) {
console.error('Failed to update provider with key:', error);
throw error;
}
},
deleteApiKey: async (providerId) => {
try {