Skip to content

Commit 312e270

Browse files
committed
Move Product Selector and Content Preview inline into chat conversation
- Add InlineBriefConfirmation component for brief confirmation in chat flow - Add InlineProductSelector component for product selection in chat flow - Add InlineContentPreview component for generated content in chat flow - Update ChatPanel to render inline components within message stream - Update App.tsx to pass handlers to ChatPanel and remove sidebar - Update CSS to hide sidebar and adjust chat panel width All interactive components now appear directly in the conversation, creating a more seamless chat-like experience.
1 parent 800bc9b commit 312e270

6 files changed

Lines changed: 757 additions & 42 deletions

File tree

content-gen/src/frontend/src/App.tsx

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import { v4 as uuidv4 } from 'uuid';
1212

1313
import { ChatPanel } from './components/ChatPanel';
1414
import { ChatHistory } from './components/ChatHistory';
15-
import { BriefConfirmation } from './components/BriefConfirmation';
16-
import { ContentPreview } from './components/ContentPreview';
17-
import { ProductSelector } from './components/ProductSelector';
1815
import type { ChatMessage, CreativeBrief, Product, GeneratedContent } from './types';
1916

2017
function App() {
@@ -314,45 +311,24 @@ function App() {
314311
/>
315312
</div>
316313

317-
{/* Chat Panel */}
318-
<div className="chat-panel">
314+
{/* Chat Panel - now includes inline product selector and content preview */}
315+
<div className="chat-panel" style={{ flex: 1 }}>
319316
<ChatPanel
320317
messages={messages}
321318
onSendMessage={handleSendMessage}
322319
isLoading={isLoading}
320+
pendingBrief={pendingBrief}
321+
confirmedBrief={confirmedBrief}
322+
generatedContent={generatedContent}
323+
selectedProducts={selectedProducts}
324+
onBriefConfirm={handleBriefConfirm}
325+
onBriefCancel={handleBriefCancel}
326+
onBriefEdit={setPendingBrief}
327+
onProductsChange={setSelectedProducts}
328+
onGenerateContent={handleGenerateContent}
329+
onRegenerateContent={handleGenerateContent}
323330
/>
324331
</div>
325-
326-
{/* Sidebar */}
327-
<div className="sidebar-panel">
328-
{/* Brief Confirmation */}
329-
{pendingBrief && (
330-
<BriefConfirmation
331-
brief={pendingBrief}
332-
onConfirm={handleBriefConfirm}
333-
onCancel={handleBriefCancel}
334-
onEdit={setPendingBrief}
335-
/>
336-
)}
337-
338-
{/* Product Selector */}
339-
{confirmedBrief && !generatedContent && (
340-
<ProductSelector
341-
selectedProducts={selectedProducts}
342-
onProductsChange={setSelectedProducts}
343-
onGenerate={handleGenerateContent}
344-
isLoading={isLoading}
345-
/>
346-
)}
347-
348-
{/* Generated Content Preview */}
349-
{generatedContent && (
350-
<ContentPreview
351-
content={generatedContent}
352-
onRegenerate={handleGenerateContent}
353-
/>
354-
)}
355-
</div>
356332
</div>
357333
</div>
358334
);

content-gen/src/frontend/src/components/ChatPanel.tsx

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,49 @@ import {
1414
Person24Regular,
1515
} from '@fluentui/react-icons';
1616
import ReactMarkdown from 'react-markdown';
17-
import type { ChatMessage } from '../types';
17+
import type { ChatMessage, CreativeBrief, Product, GeneratedContent } from '../types';
18+
import { InlineBriefConfirmation } from './InlineBriefConfirmation';
19+
import { InlineProductSelector } from './InlineProductSelector';
20+
import { InlineContentPreview } from './InlineContentPreview';
1821

1922
interface ChatPanelProps {
2023
messages: ChatMessage[];
2124
onSendMessage: (message: string) => void;
2225
isLoading: boolean;
26+
// Inline component props
27+
pendingBrief?: CreativeBrief | null;
28+
confirmedBrief?: CreativeBrief | null;
29+
generatedContent?: GeneratedContent | null;
30+
selectedProducts?: Product[];
31+
onBriefConfirm?: (brief: CreativeBrief) => void;
32+
onBriefCancel?: () => void;
33+
onBriefEdit?: (brief: CreativeBrief) => void;
34+
onProductsChange?: (products: Product[]) => void;
35+
onGenerateContent?: () => void;
36+
onRegenerateContent?: () => void;
2337
}
2438

25-
export function ChatPanel({ messages, onSendMessage, isLoading }: ChatPanelProps) {
39+
export function ChatPanel({
40+
messages,
41+
onSendMessage,
42+
isLoading,
43+
pendingBrief,
44+
confirmedBrief,
45+
generatedContent,
46+
selectedProducts = [],
47+
onBriefConfirm,
48+
onBriefCancel,
49+
onBriefEdit,
50+
onProductsChange,
51+
onGenerateContent,
52+
onRegenerateContent,
53+
}: ChatPanelProps) {
2654
const [inputValue, setInputValue] = useState('');
2755
const messagesEndRef = useRef<HTMLDivElement>(null);
2856

2957
useEffect(() => {
3058
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
31-
}, [messages]);
59+
}, [messages, pendingBrief, confirmedBrief, generatedContent]);
3260

3361
const handleSubmit = (e: React.FormEvent) => {
3462
e.preventDefault();
@@ -38,6 +66,11 @@ export function ChatPanel({ messages, onSendMessage, isLoading }: ChatPanelProps
3866
}
3967
};
4068

69+
// Determine if we should show inline components
70+
const showBriefConfirmation = pendingBrief && onBriefConfirm && onBriefCancel && onBriefEdit;
71+
const showProductSelector = confirmedBrief && !generatedContent && onProductsChange && onGenerateContent;
72+
const showContentPreview = generatedContent && onRegenerateContent;
73+
4174
return (
4275
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
4376
{/* Messages Area */}
@@ -49,7 +82,7 @@ export function ChatPanel({ messages, onSendMessage, isLoading }: ChatPanelProps
4982
flexDirection: 'column',
5083
gap: '16px'
5184
}}>
52-
{messages.length === 0 && (
85+
{messages.length === 0 && !showBriefConfirmation && !showProductSelector && !showContentPreview && (
5386
<div style={{
5487
textAlign: 'center',
5588
padding: '48px',
@@ -69,7 +102,36 @@ export function ChatPanel({ messages, onSendMessage, isLoading }: ChatPanelProps
69102
<MessageBubble key={message.id} message={message} />
70103
))}
71104

72-
{isLoading && (
105+
{/* Inline Brief Confirmation */}
106+
{showBriefConfirmation && (
107+
<InlineBriefConfirmation
108+
brief={pendingBrief}
109+
onConfirm={onBriefConfirm}
110+
onCancel={onBriefCancel}
111+
onEdit={onBriefEdit}
112+
/>
113+
)}
114+
115+
{/* Inline Product Selector */}
116+
{showProductSelector && (
117+
<InlineProductSelector
118+
selectedProducts={selectedProducts}
119+
onProductsChange={onProductsChange}
120+
onGenerate={onGenerateContent}
121+
isLoading={isLoading}
122+
/>
123+
)}
124+
125+
{/* Inline Content Preview */}
126+
{showContentPreview && (
127+
<InlineContentPreview
128+
content={generatedContent}
129+
onRegenerate={onRegenerateContent}
130+
isLoading={isLoading}
131+
/>
132+
)}
133+
134+
{isLoading && !showProductSelector && (
73135
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
74136
<Spinner size="tiny" />
75137
<Text size={200}>Generating response...</Text>
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { useState } from 'react';
2+
import {
3+
Card,
4+
Button,
5+
Input,
6+
Textarea,
7+
Text,
8+
Badge,
9+
tokens,
10+
} from '@fluentui/react-components';
11+
import {
12+
Checkmark24Regular,
13+
Dismiss24Regular,
14+
Edit24Regular,
15+
Bot24Regular,
16+
} from '@fluentui/react-icons';
17+
import type { CreativeBrief } from '../types';
18+
19+
interface InlineBriefConfirmationProps {
20+
brief: CreativeBrief;
21+
onConfirm: (brief: CreativeBrief) => void;
22+
onCancel: () => void;
23+
onEdit: (brief: CreativeBrief) => void;
24+
}
25+
26+
export function InlineBriefConfirmation({
27+
brief,
28+
onConfirm,
29+
onCancel,
30+
onEdit,
31+
}: InlineBriefConfirmationProps) {
32+
const [isEditing, setIsEditing] = useState(false);
33+
const [editedBrief, setEditedBrief] = useState<CreativeBrief>(brief);
34+
35+
const handleFieldChange = (field: keyof CreativeBrief, value: string) => {
36+
setEditedBrief(prev => ({ ...prev, [field]: value }));
37+
};
38+
39+
const handleSaveEdit = () => {
40+
onEdit(editedBrief);
41+
setIsEditing(false);
42+
};
43+
44+
const briefFields: { key: keyof CreativeBrief; label: string; multiline?: boolean }[] = [
45+
{ key: 'overview', label: 'Overview', multiline: true },
46+
{ key: 'objectives', label: 'Objectives', multiline: true },
47+
{ key: 'target_audience', label: 'Target Audience' },
48+
{ key: 'key_message', label: 'Key Message', multiline: true },
49+
{ key: 'tone_and_style', label: 'Tone & Style' },
50+
{ key: 'deliverable', label: 'Deliverable' },
51+
{ key: 'timelines', label: 'Timelines' },
52+
{ key: 'visual_guidelines', label: 'Visual Guidelines', multiline: true },
53+
{ key: 'cta', label: 'Call to Action' },
54+
];
55+
56+
return (
57+
<div style={{
58+
display: 'flex',
59+
gap: '8px',
60+
alignItems: 'flex-start',
61+
maxWidth: '100%'
62+
}}>
63+
{/* Bot Avatar */}
64+
<div style={{
65+
width: '32px',
66+
height: '32px',
67+
borderRadius: '50%',
68+
backgroundColor: tokens.colorNeutralBackground3,
69+
display: 'flex',
70+
alignItems: 'center',
71+
justifyContent: 'center',
72+
flexShrink: 0
73+
}}>
74+
<Bot24Regular style={{ fontSize: '16px' }} />
75+
</div>
76+
77+
<Card style={{
78+
flex: 1,
79+
maxWidth: 'calc(100% - 40px)',
80+
backgroundColor: tokens.colorNeutralBackground1
81+
}}>
82+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '8px' }}>
83+
<Badge appearance="outline" size="small">
84+
PlanningAgent
85+
</Badge>
86+
{!isEditing && (
87+
<Button
88+
appearance="subtle"
89+
icon={<Edit24Regular />}
90+
onClick={() => setIsEditing(true)}
91+
size="small"
92+
>
93+
Edit
94+
</Button>
95+
)}
96+
</div>
97+
98+
<Text weight="semibold" size={300} block style={{ marginBottom: '8px' }}>
99+
Confirm Your Creative Brief
100+
</Text>
101+
102+
<Text size={200} style={{ color: tokens.colorNeutralForeground3, marginBottom: '12px', display: 'block' }}>
103+
Please review the parsed brief and confirm or edit before proceeding.
104+
</Text>
105+
106+
<div style={{
107+
display: 'flex',
108+
flexDirection: 'column',
109+
gap: '8px',
110+
maxHeight: '300px',
111+
overflowY: 'auto',
112+
paddingRight: '8px'
113+
}}>
114+
{briefFields.map(({ key, label, multiline }) => (
115+
<div key={key} style={{
116+
padding: '8px',
117+
backgroundColor: tokens.colorNeutralBackground2,
118+
borderRadius: '4px'
119+
}}>
120+
<Text weight="semibold" size={100} style={{ color: tokens.colorNeutralForeground3, display: 'block', marginBottom: '2px' }}>
121+
{label}
122+
</Text>
123+
{isEditing ? (
124+
multiline ? (
125+
<Textarea
126+
value={editedBrief[key]}
127+
onChange={(e) => handleFieldChange(key, e.target.value)}
128+
resize="vertical"
129+
style={{ width: '100%' }}
130+
size="small"
131+
/>
132+
) : (
133+
<Input
134+
value={editedBrief[key]}
135+
onChange={(e) => handleFieldChange(key, e.target.value)}
136+
style={{ width: '100%' }}
137+
size="small"
138+
/>
139+
)
140+
) : (
141+
<Text
142+
size={200}
143+
style={{
144+
color: brief[key] ? tokens.colorNeutralForeground1 : tokens.colorNeutralForeground4,
145+
fontStyle: brief[key] ? 'normal' : 'italic'
146+
}}
147+
>
148+
{brief[key] || 'Not specified'}
149+
</Text>
150+
)}
151+
</div>
152+
))}
153+
</div>
154+
155+
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
156+
{isEditing ? (
157+
<>
158+
<Button
159+
appearance="primary"
160+
icon={<Checkmark24Regular />}
161+
onClick={handleSaveEdit}
162+
size="small"
163+
>
164+
Save Changes
165+
</Button>
166+
<Button
167+
appearance="subtle"
168+
onClick={() => {
169+
setEditedBrief(brief);
170+
setIsEditing(false);
171+
}}
172+
size="small"
173+
>
174+
Cancel
175+
</Button>
176+
</>
177+
) : (
178+
<>
179+
<Button
180+
appearance="primary"
181+
icon={<Checkmark24Regular />}
182+
onClick={() => onConfirm(brief)}
183+
size="small"
184+
>
185+
Confirm Brief
186+
</Button>
187+
<Button
188+
appearance="subtle"
189+
icon={<Dismiss24Regular />}
190+
onClick={onCancel}
191+
size="small"
192+
>
193+
Start Over
194+
</Button>
195+
</>
196+
)}
197+
</div>
198+
</Card>
199+
</div>
200+
);
201+
}

0 commit comments

Comments
 (0)