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;