From 0857a1f608ffbafd57a444f6b8eb099c9280f373 Mon Sep 17 00:00:00 2001 From: iven Date: Fri, 3 Apr 2026 15:20:15 +0800 Subject: [PATCH] feat(desktop): wire template welcome_message + quick_commands to chat UI - Add welcomeMessage/quickCommands fields to Clone interface - Persist template welcome/quick data via updateClone after creation - FirstConversationPrompt: prefer template-provided welcome message over dynamically generated one - FirstConversationPrompt: render template quick_commands as chips instead of hardcoded QUICK_ACTIONS when available - Tighten assign/unassign template endpoint permissions from model:read to relay:use (self-service operation for all authenticated users) --- .../zclaw-saas/src/agent_template/handlers.rs | 4 +- .../components/FirstConversationPrompt.tsx | 89 ++++++++++++------- desktop/src/store/agentStore.ts | 23 ++++- 3 files changed, 81 insertions(+), 35 deletions(-) diff --git a/crates/zclaw-saas/src/agent_template/handlers.rs b/crates/zclaw-saas/src/agent_template/handlers.rs index 3f51950..36dfd79 100644 --- a/crates/zclaw-saas/src/agent_template/handlers.rs +++ b/crates/zclaw-saas/src/agent_template/handlers.rs @@ -148,7 +148,7 @@ pub async fn assign_template( Extension(ctx): Extension, Json(req): Json, ) -> SaasResult> { - check_permission(&ctx, "model:read")?; + check_permission(&ctx, "relay:use")?; let result = service::assign_template_to_account( &state.db, &ctx.account_id, &req.template_id, @@ -174,7 +174,7 @@ pub async fn unassign_template( State(state): State, Extension(ctx): Extension, ) -> SaasResult> { - check_permission(&ctx, "model:read")?; + check_permission(&ctx, "relay:use")?; service::unassign_template(&state.db, &ctx.account_id).await?; Ok(Json(serde_json::json!({"ok": true}))) } diff --git a/desktop/src/components/FirstConversationPrompt.tsx b/desktop/src/components/FirstConversationPrompt.tsx index 247b8a1..f541b91 100644 --- a/desktop/src/components/FirstConversationPrompt.tsx +++ b/desktop/src/components/FirstConversationPrompt.tsx @@ -14,6 +14,7 @@ import { Microscope, Layers, GraduationCap, + MessageSquare, } from 'lucide-react'; import { cn } from '../lib/utils'; import { @@ -63,13 +64,15 @@ export function FirstConversationPrompt({ ultra: '多代理协作,全能力调度', }; - const welcomeMessage = generateWelcomeMessage({ - userName: clone.userName, - agentName: clone.nickname || clone.name, - emoji: clone.emoji, - personality: clone.personality, - scenarios: clone.scenarios, - }); + // Use template-provided welcome message if available, otherwise generate dynamically + const welcomeMessage = clone.welcomeMessage + || generateWelcomeMessage({ + userName: clone.userName, + agentName: clone.nickname || clone.name, + emoji: clone.emoji, + personality: clone.personality, + scenarios: clone.scenarios, + }); const handleQuickAction = (key: string) => { if (key === 'learn') { @@ -149,32 +152,54 @@ export function FirstConversationPrompt({

- {/* Quick action chips — DeerFlow-style horizontal colored pills */} + {/* Quick action chips — template-provided or DeerFlow-style defaults */}
- {QUICK_ACTIONS.map((action, index) => { - const ActionIcon = action.icon; - return ( - handleQuickAction(action.key)} - className={cn( - 'flex items-center gap-2 px-4 py-2', - 'bg-white dark:bg-gray-800', - 'border border-gray-200 dark:border-gray-700', - 'rounded-full text-sm text-gray-600 dark:text-gray-300', - 'hover:border-gray-300 dark:hover:border-gray-600', - 'hover:bg-gray-50 dark:hover:bg-gray-750', - 'transition-all duration-150' - )} - > - - {action.label} - - ); - })} + {clone.quickCommands && clone.quickCommands.length > 0 + ? clone.quickCommands.map((cmd, index) => ( + onSelectSuggestion?.(cmd.command)} + className={cn( + 'flex items-center gap-2 px-4 py-2', + 'bg-white dark:bg-gray-800', + 'border border-gray-200 dark:border-gray-700', + 'rounded-full text-sm text-gray-600 dark:text-gray-300', + 'hover:border-gray-300 dark:hover:border-gray-600', + 'hover:bg-gray-50 dark:hover:bg-gray-750', + 'transition-all duration-150' + )} + > + + {cmd.label} + + )) + : QUICK_ACTIONS.map((action, index) => { + const ActionIcon = action.icon; + return ( + handleQuickAction(action.key)} + className={cn( + 'flex items-center gap-2 px-4 py-2', + 'bg-white dark:bg-gray-800', + 'border border-gray-200 dark:border-gray-700', + 'rounded-full text-sm text-gray-600 dark:text-gray-300', + 'hover:border-gray-300 dark:hover:border-gray-600', + 'hover:bg-gray-50 dark:hover:bg-gray-750', + 'transition-all duration-150' + )} + > + + {action.label} + + ); + })}
{/* Scenario tags */} diff --git a/desktop/src/store/agentStore.ts b/desktop/src/store/agentStore.ts index 7067e51..2e0de84 100644 --- a/desktop/src/store/agentStore.ts +++ b/desktop/src/store/agentStore.ts @@ -36,6 +36,8 @@ export interface Clone { notes?: string; // 用户备注 onboardingCompleted?: boolean; // 是否完成首次引导 source_template_id?: string; // 模板来源 ID + welcomeMessage?: string; // 模板预设欢迎语 + quickCommands?: Array<{ label: string; command: string }>; // 模板预设快捷命令 } export interface UsageStats { @@ -226,10 +228,29 @@ export const useAgentStore = create((set, get) => ({ console.warn('Failed to persist temperature/max_tokens:', e); } } + + // Persist welcome_message + quick_commands as clone metadata + if (template.welcome_message || (template.quick_commands && template.quick_commands.length > 0)) { + try { + await client.updateClone(cloneId, { + welcomeMessage: template.welcome_message || '', + quickCommands: template.quick_commands || [], + }); + } catch (e) { + console.warn('Failed to persist welcome_message/quick_commands:', e); + } + } } await get().loadClones(); - return result?.clone; + + // Merge template welcome/quick data into the returned clone for immediate use + const createdClone = result?.clone as Record | undefined; + if (createdClone) { + if (template.welcome_message) createdClone.welcomeMessage = template.welcome_message; + if (template.quick_commands?.length) createdClone.quickCommands = template.quick_commands; + } + return createdClone as Clone | undefined; } catch (error) { set({ error: String(error) }); return undefined;