본문으로 건너뛰기

Chat Suggestions

Version: 1.0.0 Last Updated: 2025-01-04 Status: Implemented

1. Overview

Chat Suggestions는 채팅 응답 완료 후 사용자에게 후속 액션을 제안하는 인터랙티브 버튼 시스템입니다. SSE(Server-Sent Events)를 통해 백엔드에서 전송된 suggestion 데이터를 기반으로 렌더링됩니다.

1.1 Purpose

  • 사용자의 다음 행동을 자연스럽게 유도
  • 설문조사(Questionnaire) 시작을 쉽게 제안
  • LLM이 생성한 후속 질문으로 대화 흐름 확장
  • Conversational Action Catalog의 액션 실행 지원

1.2 Key Features

FeatureDescription
3가지 Suggestion 타입Exploration, Questionnaire, Action
애니메이션 효과Framer Motion 기반 fade-in, scale 애니메이션
타입별 스타일링아이콘 및 색상으로 타입 구분
SSE 이벤트 연동실시간 suggestion 수신 및 렌더링

2. Type Definitions

2.1 Suggestion Types

// lib/chat/types.ts

/**
* Questionnaire Type Enum
* Matches backend QuestionnaireType enum
*/
export type QuestionnaireType = 'ISI' | 'DBAS16' | 'PHQ9' | 'GAD7' | 'PSS' | 'WIS';

/**
* Exploration Suggestion
* Follow-up questions or topics generated by LLM
*/
export interface ExplorationSuggestion {
type: 'exploration';
text: string;
}

/**
* Questionnaire Suggestion
* Suggestion to start a questionnaire
*/
export interface QuestionnaireSuggestion {
type: 'questionnaire';
questionnaireType: QuestionnaireType;
text: string;
actionId?: string;
}

/**
* Action Suggestion (generic)
* Actions from the Conversational Action Catalog
*/
export interface ActionSuggestion {
type: 'action';
actionId: string;
text: string;
actionTypeId: string;
payload: Record<string, unknown>;
confidence: number;
}

/**
* Combined Suggestion Type
*/
export type Suggestion = ExplorationSuggestion | QuestionnaireSuggestion | ActionSuggestion;

2.2 SSE Event Structure

// lib/chat/chat-api.ts

export interface SSEEvent {
type: 'agent_switch' | 'content' | 'control' | 'error' | 'suggestions';
// ... other fields

// Suggestions event data
suggestions?: Suggestion[];
suggestionsMetadata?: {
explorationCount: number;
actionCount: number;
totalGenerated: number;
};
}

3. Component Architecture

3.1 Component Hierarchy

Chat (chat.tsx)
├── NativeMessages (native-messages.tsx)
│ └── SuggestionButtons (suggestion-buttons.tsx)
│ └── SuggestionButton (internal)
├── NativeChatInput
└── QuestionnaireModal

3.2 SuggestionButtons Component

Location: components/chat/suggestion-buttons.tsx

interface SuggestionButtonsProps {
suggestions: Suggestion[];
onSelect: (suggestion: Suggestion) => void;
disabled?: boolean;
}

export function SuggestionButtons({
suggestions,
onSelect,
disabled = false,
}: SuggestionButtonsProps) {
if (!suggestions || suggestions.length === 0) {
return null;
}

return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="flex flex-wrap gap-2 py-3"
>
{suggestions.map((suggestion, index) => (
<SuggestionButton
key={`${suggestion.type}-${index}`}
suggestion={suggestion}
onSelect={onSelect}
disabled={disabled}
index={index}
/>
))}
</motion.div>
);
}

3.3 Type-specific Icons

TypeIconVisual
questionnaireCheckmark with boxSurvey/form indicator
explorationMagnifying glassSearch/discover indicator
actionArrow rightAction/proceed indicator

3.4 Styling by Type

TypeBackgroundText ColorShadow
questionnaire#3651B2 (Primary Blue)Whitergba(54, 81, 178, 0.3)
exploration#F3F4F6 (Light Gray)#1A1B1E (Dark)rgba(0, 0, 0, 0.1)
action#F3F4F6 (Light Gray)#1A1B1E (Dark)rgba(0, 0, 0, 0.1)

4. Data Flow

4.1 SSE Event Flow

┌─────────────────┐
│ Backend SSE │
│ (suggestions) │
└────────┬────────┘


┌─────────────────┐
│ chat-api.ts │
│ (SSE parsing) │
└────────┬────────┘


┌─────────────────┐
│ useChatStream │
│ setSuggestions()│
└────────┬────────┘


┌─────────────────┐
│ NativeMessages │
│ (render if │
│ !isStreaming) │
└────────┬────────┘


┌─────────────────┐
│SuggestionButtons│
│ (UI render) │
└─────────────────┘

4.2 State Management

// hooks/chat/use-chat-stream.ts

export function useChatStream(options: UseChatStreamOptions): UseChatStreamReturn {
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);

// SSE event handling
case 'suggestions': {
console.log('[useChatStream] suggestions event:', event.suggestions);
if (event.suggestions && event.suggestions.length > 0) {
setSuggestions(event.suggestions);
}
break;
}

// Clear suggestions when sending new message
const sendMessage = useCallback((content: string) => {
setSuggestions([]); // Clear previous suggestions
// ... send message logic
}, []);

const clearSuggestions = useCallback(() => {
setSuggestions([]);
}, []);

return {
suggestions,
clearSuggestions,
// ... other returns
};
}

5. Event Handling

5.1 Selection Handler

// components/chat/chat.tsx

const handleSelectSuggestion = useCallback((suggestion: Suggestion) => {
console.log('[Chat] Suggestion selected:', suggestion);

if (suggestion.type === 'questionnaire') {
// Open questionnaire modal
const qnSuggestion = suggestion as QuestionnaireSuggestion;
setActiveQuestionnaireType(qnSuggestion.questionnaireType as QuestionnaireType);
setIsQuestionnaireOpen(true);
clearSuggestions();
} else if (suggestion.type === 'exploration') {
// Send the suggestion text as a message
sendMessage(suggestion.text);
clearSuggestions();
} else if (suggestion.type === 'action') {
// Handle action suggestions (future implementation)
console.log('[Chat] Action suggestion not implemented yet:', suggestion);
}
}, [clearSuggestions, sendMessage]);

5.2 Behavior by Type

TypeClick Behavior
questionnaireOpens QuestionnaireModal with the specified type
explorationSends the suggestion text as a new user message
action(Not implemented) Logs to console

6. Rendering Conditions

Suggestions are only rendered when ALL conditions are met:

// components/chat/native-messages.tsx

{!isStreaming && suggestions.length > 0 && onSelectSuggestion && (
<div className="px-1">
<SuggestionButtons
suggestions={suggestions}
onSelect={onSelectSuggestion}
disabled={isStreaming}
/>
</div>
)}
ConditionPurpose
!isStreamingDon't show during response generation
suggestions.length > 0Only show when suggestions exist
onSelectSuggestionHandler must be provided

7. Animation Specs

7.1 Container Animation

<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="flex flex-wrap gap-2 py-3"
>

7.2 Button Animation

<motion.button
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1, duration: 0.2 }}
className="... hover:scale-[1.02] active:scale-[0.98]"
>
PropertyValueDescription
Initial opacity0Fade in from invisible
Initial y10pxSlide up from below
Stagger delayindex * 0.1sSequential appearance
Duration0.2sQuick, snappy animation
Hover scale1.02Subtle grow on hover
Active scale0.98Subtle shrink on press

8. Backend Integration

8.1 Expected SSE Event Format

{
"type": "suggestions",
"suggestions": [
{
"type": "exploration",
"text": "Tell me more about your sleep patterns"
},
{
"type": "questionnaire",
"questionnaireType": "ISI",
"text": "Complete sleep quality assessment"
},
{
"type": "action",
"actionId": "set-bedtime-goal",
"text": "Set a bedtime goal",
"actionTypeId": "sleep.goal.set",
"payload": { "suggestedTime": "23:00" },
"confidence": 0.85
}
],
"suggestionsMetadata": {
"explorationCount": 1,
"actionCount": 1,
"totalGenerated": 3
}
}

8.2 Timing

  • Suggestions event is sent after the control (done) event
  • Suggestions appear only after streaming completes

9. Future Improvements

9.1 Action Suggestion Implementation

Currently, ActionSuggestion type selection only logs to console. Full implementation requires:

  1. Action execution service integration
  2. Conversational Action Catalog lookup
  3. Payload validation and processing
  4. Result feedback to chat

9.2 Potential Enhancements

  • Suggestion analytics tracking
  • A/B testing for suggestion ordering
  • Personalized suggestion ranking
  • Suggestion expiration/timeout
  • Multi-language support for suggestion text

10. File References

FilePurpose
lib/chat/types.tsType definitions
lib/chat/chat-api.tsSSE event interface
hooks/chat/use-chat-stream.tsState management
components/chat/suggestion-buttons.tsxUI component
components/chat/native-messages.tsxRendering logic
components/chat/chat.tsxEvent handling

11. Change History

VersionDateAuthorChanges
1.0.02025-01-04bok@weltcorp.comInitial documentation