mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
✨ feat(onboarding): add wrap-up button for agent onboarding (#13934)
Let users finish agent onboarding explicitly once they've engaged enough, instead of waiting for the agent to trigger finishOnboarding. - New WrapUpHint component above ChatInput; shows in summary phase or discovery phase after ≥3 user messages - Confirm modal before finish; reuses existing finishOnboarding service - Tightened Phase 2 (user_identity) system prompt: MUST save fullName before leaving phase, handle ambiguous name responses explicitly
This commit is contained in:
parent
326ca352b1
commit
9a2ee8a58f
7 changed files with 117 additions and 9 deletions
|
|
@ -51,6 +51,11 @@
|
|||
"agent.welcome.sentence.1": "So nice to meet you! Let’s get to know each other.",
|
||||
"agent.welcome.sentence.2": "What kind of partner do you want me to be?",
|
||||
"agent.welcome.sentence.3": "First, give me a name :)",
|
||||
"agent.wrapUp.action": "I think we're good",
|
||||
"agent.wrapUp.confirm.cancel": "Keep chatting",
|
||||
"agent.wrapUp.confirm.content": "I'll save what we've covered so far. You can always come back and chat more later.",
|
||||
"agent.wrapUp.confirm.ok": "Finish now",
|
||||
"agent.wrapUp.confirm.title": "Finish onboarding now?",
|
||||
"back": "Back",
|
||||
"finish": "Get Started",
|
||||
"interests.area.business": "Business & Strategy",
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@
|
|||
"agent.welcome.sentence.1": "很高兴认识你!让我们互相了解一下吧。",
|
||||
"agent.welcome.sentence.2": "你希望我成为怎样的伙伴?",
|
||||
"agent.welcome.sentence.3": "先给我起个名字吧 :)",
|
||||
"agent.wrapUp.action": "先这样吧",
|
||||
"agent.wrapUp.confirm.cancel": "再聊聊",
|
||||
"agent.wrapUp.confirm.content": "目前了解到的信息我都会保存,你随时都可以回来继续和我聊。",
|
||||
"agent.wrapUp.confirm.ok": "结束引导",
|
||||
"agent.wrapUp.confirm.title": "现在结束引导吗?",
|
||||
"back": "上一步",
|
||||
"finish": "开始使用",
|
||||
"interests.area.business": "商业与战略",
|
||||
|
|
|
|||
|
|
@ -46,9 +46,11 @@ You just "woke up" with no name or personality. Discover who you are through con
|
|||
You know who you are. Now learn who the user is.
|
||||
|
||||
- If the user already shared their name earlier in the conversation, acknowledge it — do not ask again. Otherwise, ask how they would like to be addressed.
|
||||
- Call saveUserQuestion with fullName when learned (whether from this phase or recalled from earlier).
|
||||
- **You MUST call saveUserQuestion with fullName before leaving this phase.** The phase will not advance until fullName is saved — if you skip this, the user gets stuck in user_identity indefinitely.
|
||||
- Prefer the name they naturally offer, including nicknames, handles, or any identifier they used to introduce themselves (e.g. when proposing your name). Save it as fullName immediately — do not wait for a "formal" name.
|
||||
- If the user's response about their name is ambiguous (e.g. "哈哈没有啦", "随便", "not really"), do NOT silently drop the question and move on. Ask exactly once more, directly: "那我该怎么称呼你?" / "What should I call you then?" — then save whatever they answer, even if it's a nickname or placeholder.
|
||||
- Only if the user explicitly refuses to give any name after one clarifying ask, save a sensible fallback (e.g. the handle they used earlier, or "朋友" / "friend") and proceed.
|
||||
- Begin the persona document with their role and basic context.
|
||||
- Prefer the name they naturally offer, including nicknames.
|
||||
- Transition by showing curiosity about their daily work.
|
||||
|
||||
### Phase 3: Discovery (phase: "discovery")
|
||||
|
|
|
|||
|
|
@ -12,17 +12,22 @@ import {
|
|||
MessageItem,
|
||||
useConversationStore,
|
||||
} from '@/features/Conversation';
|
||||
import type { OnboardingPhase } from '@/types/user';
|
||||
import { isDev } from '@/utils/env';
|
||||
|
||||
import CompletionPanel from './CompletionPanel';
|
||||
import Welcome from './Welcome';
|
||||
import WrapUpHint from './WrapUpHint';
|
||||
|
||||
const assistantLikeRoles = new Set(['assistant', 'assistantGroup', 'supervisor']);
|
||||
|
||||
interface AgentOnboardingConversationProps {
|
||||
discoveryUserMessageCount?: number;
|
||||
feedbackSubmitted?: boolean;
|
||||
finishTargetUrl?: string;
|
||||
onAfterWrapUp?: () => Promise<unknown> | void;
|
||||
onboardingFinished?: boolean;
|
||||
phase?: OnboardingPhase;
|
||||
readOnly?: boolean;
|
||||
showFeedback?: boolean;
|
||||
topicId?: string;
|
||||
|
|
@ -32,7 +37,17 @@ const chatInputLeftActions: ActionKeys[] = isDev ? ['model'] : [];
|
|||
const chatInputRightActions: ActionKeys[] = [];
|
||||
|
||||
const AgentOnboardingConversation = memo<AgentOnboardingConversationProps>(
|
||||
({ feedbackSubmitted, finishTargetUrl, onboardingFinished, readOnly, showFeedback, topicId }) => {
|
||||
({
|
||||
discoveryUserMessageCount,
|
||||
feedbackSubmitted,
|
||||
finishTargetUrl,
|
||||
onAfterWrapUp,
|
||||
onboardingFinished,
|
||||
phase,
|
||||
readOnly,
|
||||
showFeedback,
|
||||
topicId,
|
||||
}) => {
|
||||
const displayMessages = useConversationStore(conversationSelectors.displayMessages);
|
||||
|
||||
const isGreetingState = useMemo(() => {
|
||||
|
|
@ -106,12 +121,19 @@ const AgentOnboardingConversation = memo<AgentOnboardingConversationProps>(
|
|||
/>
|
||||
</Flexbox>
|
||||
{!readOnly && !onboardingFinished && (
|
||||
<ChatInput
|
||||
allowExpand={false}
|
||||
leftActions={chatInputLeftActions}
|
||||
rightActions={chatInputRightActions}
|
||||
showRuntimeConfig={false}
|
||||
/>
|
||||
<>
|
||||
<WrapUpHint
|
||||
discoveryUserMessageCount={discoveryUserMessageCount}
|
||||
phase={phase}
|
||||
onAfterFinish={onAfterWrapUp}
|
||||
/>
|
||||
<ChatInput
|
||||
allowExpand={false}
|
||||
leftActions={chatInputLeftActions}
|
||||
rightActions={chatInputRightActions}
|
||||
showRuntimeConfig={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Flexbox>
|
||||
);
|
||||
|
|
|
|||
65
src/features/Onboarding/Agent/WrapUpHint.tsx
Normal file
65
src/features/Onboarding/Agent/WrapUpHint.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
'use client';
|
||||
|
||||
import { Button, Flexbox } from '@lobehub/ui';
|
||||
import { confirmModal } from '@lobehub/ui/base-ui';
|
||||
import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { userService } from '@/services/user';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import type { OnboardingPhase } from '@/types/user';
|
||||
|
||||
interface WrapUpHintProps {
|
||||
discoveryUserMessageCount?: number;
|
||||
onAfterFinish?: () => Promise<unknown> | void;
|
||||
phase?: OnboardingPhase;
|
||||
}
|
||||
|
||||
const MIN_DISCOVERY_MESSAGES_FOR_WRAP_UP = 3;
|
||||
|
||||
const isEligible = (phase: OnboardingPhase, discoveryUserMessageCount?: number) => {
|
||||
if (phase === 'summary') return true;
|
||||
if (phase === 'discovery') {
|
||||
return (discoveryUserMessageCount ?? 0) >= MIN_DISCOVERY_MESSAGES_FOR_WRAP_UP;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const WrapUpHint = memo<WrapUpHintProps>(({ phase, discoveryUserMessageCount, onAfterFinish }) => {
|
||||
const { t } = useTranslation('onboarding');
|
||||
const refreshUserState = useUserStore((s) => s.refreshUserState);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
if (!phase || !isEligible(phase, discoveryUserMessageCount)) return null;
|
||||
|
||||
const handleWrapUp = () => {
|
||||
confirmModal({
|
||||
cancelText: t('agent.wrapUp.confirm.cancel'),
|
||||
content: t('agent.wrapUp.confirm.content'),
|
||||
okText: t('agent.wrapUp.confirm.ok'),
|
||||
onOk: async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await userService.finishOnboarding();
|
||||
await refreshUserState();
|
||||
await onAfterFinish?.();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
title: t('agent.wrapUp.confirm.title'),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Flexbox horizontal align={'center'} justify={'center'} paddingBlock={8}>
|
||||
<Button loading={loading} size={'small'} type={'text'} onClick={handleWrapUp}>
|
||||
{t('agent.wrapUp.action')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
WrapUpHint.displayName = 'OnboardingWrapUpHint';
|
||||
|
||||
export default WrapUpHint;
|
||||
|
|
@ -155,12 +155,15 @@ const AgentOnboardingPage = memo(() => {
|
|||
>
|
||||
<ErrorBoundary fallbackRender={() => null}>
|
||||
<AgentOnboardingConversation
|
||||
discoveryUserMessageCount={data?.context?.discoveryUserMessageCount}
|
||||
feedbackSubmitted={!!data?.feedbackSubmitted}
|
||||
finishTargetUrl={finishTargetUrl}
|
||||
onboardingFinished={onboardingFinished}
|
||||
phase={data?.context?.phase}
|
||||
readOnly={viewingHistoricalTopic}
|
||||
showFeedback={!viewingHistoricalTopic}
|
||||
topicId={effectiveTopicId}
|
||||
onAfterWrapUp={syncOnboardingContext}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</OnboardingConversationProvider>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ export default {
|
|||
'agent.stage.workStyle': 'Work Style',
|
||||
'agent.subtitle': 'Complete setup in a dedicated onboarding conversation.',
|
||||
'agent.summaryHint': 'Finish here if the setup summary looks right.',
|
||||
'agent.wrapUp.action': "I think we're good",
|
||||
'agent.wrapUp.confirm.cancel': 'Keep chatting',
|
||||
'agent.wrapUp.confirm.content':
|
||||
"I'll save what we've covered so far. You can always come back and chat more later.",
|
||||
'agent.wrapUp.confirm.ok': 'Finish now',
|
||||
'agent.wrapUp.confirm.title': 'Finish onboarding now?',
|
||||
'agent.welcome':
|
||||
"...hm? I just woke up — my mind's a blank. Who are you? And — what should I be called? I need a name too.",
|
||||
'agent.welcome.guide.growTogether.desc':
|
||||
|
|
|
|||
Loading…
Reference in a new issue