Skip to content

Commit 334fc59

Browse files
fix: added test coverage
2 parents ee9fb41 + dc72aa0 commit 334fc59

File tree

10 files changed

+1169
-27
lines changed

10 files changed

+1169
-27
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import ContosoLogo from './styles/images/contoso.svg';
2020

2121
function App() {
2222
const [conversationId, setConversationId] = useState<string>(() => uuidv4());
23+
const [conversationTitle, setConversationTitle] = useState<string | null>(null);
2324
const [userId, setUserId] = useState<string>('');
2425
const [userName, setUserName] = useState<string>('');
2526
const [messages, setMessages] = useState<ChatMessage[]>([]);
@@ -104,6 +105,7 @@ function App() {
104105
if (response.ok) {
105106
const data = await response.json();
106107
setConversationId(selectedConversationId);
108+
setConversationTitle(null); // Will use title from conversation list
107109
const loadedMessages: ChatMessage[] = (data.messages || []).map((msg: { role: string; content: string; timestamp?: string; agent?: string }, index: number) => ({
108110
id: `${selectedConversationId}-${index}`,
109111
role: msg.role as 'user' | 'assistant',
@@ -189,6 +191,7 @@ function App() {
189191
// Handle starting a new conversation
190192
const handleNewConversation = useCallback(() => {
191193
setConversationId(uuidv4());
194+
setConversationTitle(null);
192195
setMessages([]);
193196
setPendingBrief(null);
194197
setAwaitingClarification(false);
@@ -230,6 +233,9 @@ function App() {
230233

231234
setGenerationStatus('Updating creative brief...');
232235
const parsed = await parseBrief(refinementPrompt, conversationId, userId, signal);
236+
if (parsed.generated_title && !conversationTitle) {
237+
setConversationTitle(parsed.generated_title);
238+
}
233239
if (parsed.brief) {
234240
setPendingBrief(parsed.brief);
235241
}
@@ -467,6 +473,11 @@ function App() {
467473
setGenerationStatus('Analyzing creative brief...');
468474
const parsed = await parseBrief(content, conversationId, userId, signal);
469475

476+
// Set conversation title from generated title
477+
if (parsed.generated_title && !conversationTitle) {
478+
setConversationTitle(parsed.generated_title);
479+
}
480+
470481
// Check if request was blocked due to harmful content
471482
if (parsed.rai_blocked) {
472483
// Show the refusal message without any brief UI
@@ -838,6 +849,7 @@ function App() {
838849
<div className="history-panel">
839850
<ChatHistory
840851
currentConversationId={conversationId}
852+
currentConversationTitle={conversationTitle}
841853
currentMessages={messages}
842854
onSelectConversation={handleSelectConversation}
843855
onNewConversation={handleNewConversation}

content-gen/src/app/frontend/src/components/ChatHistory.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ interface ConversationSummary {
3737

3838
interface ChatHistoryProps {
3939
currentConversationId: string;
40+
currentConversationTitle?: string | null;
4041
currentMessages?: { role: string; content: string }[]; // Current session messages
4142
onSelectConversation: (conversationId: string) => void;
4243
onNewConversation: () => void;
@@ -46,6 +47,7 @@ interface ChatHistoryProps {
4647

4748
export function ChatHistory({
4849
currentConversationId,
50+
currentConversationTitle,
4951
currentMessages = [],
5052
onSelectConversation,
5153
onNewConversation,
@@ -152,13 +154,14 @@ export function ChatHistory({
152154
}, [refreshTrigger]);
153155

154156
// Build the current session conversation summary if it has messages
155-
const currentSessionConversation: ConversationSummary | null = currentMessages.length > 0 ? {
156-
id: currentConversationId,
157-
title: currentMessages.find(m => m.role === 'user')?.content?.substring(0, 50) || 'Current Conversation',
158-
lastMessage: currentMessages[currentMessages.length - 1]?.content?.substring(0, 100) || '',
159-
timestamp: new Date().toISOString(),
160-
messageCount: currentMessages.length,
161-
} : null;
157+
const currentSessionConversation: ConversationSummary | null =
158+
currentMessages.length > 0 && currentConversationTitle ? {
159+
id: currentConversationId,
160+
title: currentConversationTitle,
161+
lastMessage: currentMessages[currentMessages.length - 1]?.content?.substring(0, 100) || '',
162+
timestamp: new Date().toISOString(),
163+
messageCount: currentMessages.length,
164+
} : null;
162165

163166
// Merge current session with saved conversations, updating the current one with live data
164167
const displayConversations = (() => {

content-gen/src/app/frontend/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export interface ParsedBriefResponse {
9292
rai_blocked?: boolean;
9393
message: string;
9494
conversation_id?: string;
95+
generated_title?: string;
9596
}
9697

9798
export interface GeneratedContent {

content-gen/src/backend/app.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from orchestrator import get_orchestrator
2323
from services.cosmos_service import get_cosmos_service
2424
from services.blob_service import get_blob_service
25+
from services.title_service import get_title_service
2526
from api.admin import admin_bp
2627

2728
# In-memory task storage for generation tasks
@@ -107,14 +108,25 @@ async def chat():
107108
# Try to save to CosmosDB but don't fail if it's unavailable
108109
try:
109110
cosmos_service = await get_cosmos_service()
111+
112+
generated_title = None
113+
existing_conversation = await cosmos_service.get_conversation(conversation_id, user_id)
114+
existing_metadata = existing_conversation.get("metadata", {}) if existing_conversation else {}
115+
has_existing_title = bool(existing_metadata.get("custom_title") or existing_metadata.get("generated_title"))
116+
117+
if not has_existing_title:
118+
title_service = get_title_service()
119+
generated_title = await title_service.generate_title(message)
120+
110121
await cosmos_service.add_message_to_conversation(
111122
conversation_id=conversation_id,
112123
user_id=user_id,
113124
message={
114125
"role": "user",
115126
"content": message,
116127
"timestamp": datetime.now(timezone.utc).isoformat()
117-
}
128+
},
129+
generated_title=generated_title
118130
)
119131
except Exception as e:
120132
logger.warning(f"Failed to save message to CosmosDB: {e}")
@@ -188,22 +200,35 @@ async def parse_brief():
188200
if not brief_text:
189201
return jsonify({"error": "Brief text is required"}), 400
190202

203+
orchestrator = get_orchestrator()
204+
generated_title = None
205+
191206
# Save the user's brief text as a message to CosmosDB
192207
try:
193208
cosmos_service = await get_cosmos_service()
209+
210+
# Generate title for new conversations
211+
existing_conversation = await cosmos_service.get_conversation(conversation_id, user_id)
212+
existing_metadata = existing_conversation.get("metadata", {}) if existing_conversation else {}
213+
has_existing_title = bool(existing_metadata.get("custom_title") or existing_metadata.get("generated_title"))
214+
215+
if not has_existing_title:
216+
title_service = get_title_service()
217+
generated_title = await title_service.generate_title(brief_text)
218+
194219
await cosmos_service.add_message_to_conversation(
195220
conversation_id=conversation_id,
196221
user_id=user_id,
197222
message={
198223
"role": "user",
199224
"content": brief_text,
200225
"timestamp": datetime.now(timezone.utc).isoformat()
201-
}
226+
},
227+
generated_title=generated_title
202228
)
203229
except Exception as e:
204230
logger.warning(f"Failed to save brief message to CosmosDB: {e}")
205231

206-
orchestrator = get_orchestrator()
207232
parsed_brief, clarifying_questions, rai_blocked = await orchestrator.parse_brief(brief_text)
208233

209234
# Check if request was blocked due to harmful content
@@ -229,6 +254,7 @@ async def parse_brief():
229254
"requires_clarification": False,
230255
"requires_confirmation": False,
231256
"conversation_id": conversation_id,
257+
"generated_title": generated_title,
232258
"message": clarifying_questions
233259
})
234260

@@ -256,6 +282,7 @@ async def parse_brief():
256282
"requires_confirmation": False,
257283
"clarifying_questions": clarifying_questions,
258284
"conversation_id": conversation_id,
285+
"generated_title": generated_title,
259286
"message": clarifying_questions
260287
})
261288

@@ -280,6 +307,7 @@ async def parse_brief():
280307
"requires_clarification": False,
281308
"requires_confirmation": True,
282309
"conversation_id": conversation_id,
310+
"generated_title": generated_title,
283311
"message": "Please review and confirm the parsed creative brief"
284312
})
285313

@@ -1365,12 +1393,12 @@ async def update_conversation(conversation_id: str):
13651393
async def delete_all_conversations():
13661394
"""
13671395
Delete all conversations for the current user.
1368-
1396+
13691397
Uses authenticated user from EasyAuth headers.
13701398
"""
13711399
auth_user = get_authenticated_user()
13721400
user_id = auth_user["user_principal_id"]
1373-
1401+
13741402
try:
13751403
cosmos_service = await get_cosmos_service()
13761404
deleted_count = await cosmos_service.delete_all_conversations(user_id)

content-gen/src/backend/services/cosmos_service.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -343,13 +343,27 @@ async def save_conversation(
343343
"""
344344
await self.initialize()
345345

346+
# Get existing conversation to preserve important metadata fields
347+
existing = await self.get_conversation(conversation_id, user_id)
348+
existing_metadata = existing.get("metadata", {}) if existing else {}
349+
350+
# Merge metadata - preserve generated_title and custom_title from existing
351+
merged_metadata = {}
352+
if existing_metadata.get("generated_title"):
353+
merged_metadata["generated_title"] = existing_metadata["generated_title"]
354+
if existing_metadata.get("custom_title"):
355+
merged_metadata["custom_title"] = existing_metadata["custom_title"]
356+
# Add new metadata on top
357+
if metadata:
358+
merged_metadata.update(metadata)
359+
346360
item = {
347361
"id": conversation_id,
348362
"userId": user_id, # Partition key field (matches container definition /userId)
349363
"user_id": user_id, # Keep for backward compatibility
350364
"messages": messages,
351365
"brief": brief.model_dump() if brief else None,
352-
"metadata": metadata or {},
366+
"metadata": merged_metadata,
353367
"generated_content": generated_content,
354368
"updated_at": datetime.now(timezone.utc).isoformat()
355369
}
@@ -401,7 +415,8 @@ async def add_message_to_conversation(
401415
self,
402416
conversation_id: str,
403417
user_id: str,
404-
message: dict
418+
message: dict,
419+
generated_title: Optional[str] = None
405420
) -> dict:
406421
"""
407422
Add a message to an existing conversation.
@@ -422,6 +437,12 @@ async def add_message_to_conversation(
422437
# Ensure userId is set (for partition key) - migrate old documents
423438
if not conversation.get("userId"):
424439
conversation["userId"] = conversation.get("user_id") or user_id
440+
conversation["metadata"] = conversation.get("metadata", {})
441+
if generated_title:
442+
has_custom_title = bool(conversation["metadata"].get("custom_title"))
443+
has_generated_title = bool(conversation["metadata"].get("generated_title"))
444+
if not has_custom_title and not has_generated_title:
445+
conversation["metadata"]["generated_title"] = generated_title
425446
conversation["messages"].append(message)
426447
conversation["updated_at"] = datetime.now(timezone.utc).isoformat()
427448
else:
@@ -430,6 +451,7 @@ async def add_message_to_conversation(
430451
"userId": user_id, # Partition key field
431452
"user_id": user_id, # Keep for backward compatibility
432453
"messages": [message],
454+
"metadata": {"generated_title": generated_title} if generated_title else {},
433455
"updated_at": datetime.now(timezone.utc).isoformat()
434456
}
435457

@@ -494,16 +516,21 @@ async def get_user_conversations(
494516
custom_title = metadata.get("custom_title") if metadata else None
495517
if custom_title:
496518
title = custom_title
519+
elif metadata and metadata.get("generated_title"):
520+
title = metadata.get("generated_title")
497521
elif brief and brief.get("overview"):
498-
title = brief["overview"][:50]
522+
overview_words = brief["overview"].split()[:4]
523+
title = " ".join(overview_words) if overview_words else "New Conversation"
499524
elif messages:
500-
title = "Untitled Conversation"
525+
title = "New Conversation"
501526
for msg in messages:
502527
if msg.get("role") == "user":
503-
title = msg.get("content", "")[:50]
528+
content = msg.get("content", "")
529+
words = content.split()[:4]
530+
title = " ".join(words) if words else "New Conversation"
504531
break
505532
else:
506-
title = "Untitled Conversation"
533+
title = "New Conversation"
507534

508535
# Get last message preview
509536
last_message = ""
@@ -597,26 +624,26 @@ async def delete_all_conversations(
597624
) -> int:
598625
"""
599626
Delete all conversations for a user.
600-
627+
601628
Args:
602629
user_id: User ID to delete conversations for
603-
630+
604631
Returns:
605632
Number of conversations deleted
606633
"""
607634
await self.initialize()
608-
635+
609636
# First get all conversations for the user
610637
conversations = await self.get_user_conversations(user_id, limit=1000)
611-
638+
612639
deleted_count = 0
613640
for conv in conversations:
614641
try:
615642
await self.delete_conversation(conv["id"], user_id)
616643
deleted_count += 1
617644
except Exception as e:
618645
logger.warning(f"Failed to delete conversation {conv['id']}: {e}")
619-
646+
620647
logger.info(f"Deleted {deleted_count} conversations for user {user_id}")
621648
return deleted_count
622649

0 commit comments

Comments
 (0)