feat(desktop): integrate SaaS llm_routing, template API, and onboarding template selection

- Add AgentTemplateAvailable/AgentTemplateFull types and fetchAvailableTemplates/fetchTemplateFull API methods to saas-client
- Add llm_routing field to SaaSAccountInfo for admin-configured routing priority
- Add availableTemplates state and fetchAvailableTemplates action to saasStore with background fetch on login
- Add admin llm_routing priority check in connectionStore connect() to force relay or local mode
- Add createFromTemplate action to agentStore with SOUL.md persistence
- Add Step 0 template selection to AgentOnboardingWizard with grid layout for template browsing
This commit is contained in:
iven
2026-03-31 03:15:45 +08:00
parent 9fb9c3204c
commit c9b9c5231b
6 changed files with 927 additions and 1025 deletions

View File

@@ -6,6 +6,7 @@
*/
import { create } from 'zustand';
import type { GatewayClient } from '../lib/gateway-client';
import type { AgentTemplateFull } from '../lib/saas-client';
import { useChatStore } from './chatStore';
// === Types ===
@@ -33,6 +34,7 @@ export interface Clone {
communicationStyle?: string; // 沟通风格描述
notes?: string; // 用户备注
onboardingCompleted?: boolean; // 是否完成首次引导
source_template_id?: string; // 模板来源 ID
}
export interface UsageStats {
@@ -83,6 +85,7 @@ export interface AgentStateSlice {
export interface AgentActionsSlice {
loadClones: () => Promise<void>;
createClone: (opts: CloneCreateOptions) => Promise<Clone | undefined>;
createFromTemplate: (template: AgentTemplateFull) => Promise<Clone | undefined>;
updateClone: (id: string, updates: Partial<Clone>) => Promise<Clone | undefined>;
deleteClone: (id: string) => Promise<void>;
loadUsageStats: () => Promise<void>;
@@ -173,6 +176,43 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
}
},
createFromTemplate: async (template: AgentTemplateFull) => {
const client = getClient();
if (!client) return undefined;
set({ isLoading: true, error: null });
try {
const result = await client.createClone({
name: template.name,
emoji: template.emoji,
personality: template.personality,
scenarios: template.scenarios,
communicationStyle: template.communication_style,
model: template.model,
});
const cloneId = result?.clone?.id;
// Persist SOUL.md via identity system
if (cloneId && template.soul_content) {
try {
const { intelligenceClient } = await import('../lib/intelligence-client');
await intelligenceClient.identity.updateFile(cloneId, 'soul', template.soul_content);
} catch (e) {
console.warn('Failed to persist soul_content:', e);
}
}
await get().loadClones();
return result?.clone;
} catch (error) {
set({ error: String(error) });
return undefined;
} finally {
set({ isLoading: false });
}
},
updateClone: async (id: string, updates: Partial<Clone>) => {
const client = getClient();
if (!client) {