Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions content-gen/src/app/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import ContosoLogo from './styles/images/contoso.svg';

function App() {
const [conversationId, setConversationId] = useState<string>(() => uuidv4());
const [conversationTitle, setConversationTitle] = useState<string | null>(null);
const [userId, setUserId] = useState<string>('');
const [userName, setUserName] = useState<string>('');
const [messages, setMessages] = useState<ChatMessage[]>([]);
Expand Down Expand Up @@ -104,6 +105,7 @@ function App() {
if (response.ok) {
const data = await response.json();
setConversationId(selectedConversationId);
setConversationTitle(null); // Will use title from conversation list
const loadedMessages: ChatMessage[] = (data.messages || []).map((msg: { role: string; content: string; timestamp?: string; agent?: string }, index: number) => ({
id: `${selectedConversationId}-${index}`,
role: msg.role as 'user' | 'assistant',
Expand Down Expand Up @@ -175,6 +177,7 @@ function App() {
// Handle starting a new conversation
const handleNewConversation = useCallback(() => {
setConversationId(uuidv4());
setConversationTitle(null);
setMessages([]);
setPendingBrief(null);
setAwaitingClarification(false);
Expand Down Expand Up @@ -216,6 +219,9 @@ function App() {

setGenerationStatus('Updating creative brief...');
const parsed = await parseBrief(refinementPrompt, conversationId, userId, signal);
if (parsed.generated_title && !conversationTitle) {
setConversationTitle(parsed.generated_title);
}
if (parsed.brief) {
setPendingBrief(parsed.brief);
}
Expand Down Expand Up @@ -428,6 +434,11 @@ function App() {
setGenerationStatus('Analyzing creative brief...');
const parsed = await parseBrief(content, conversationId, userId, signal);

// Set conversation title from generated title
if (parsed.generated_title && !conversationTitle) {
setConversationTitle(parsed.generated_title);
}

// Check if request was blocked due to harmful content
if (parsed.rai_blocked) {
// Show the refusal message without any brief UI
Expand Down Expand Up @@ -799,6 +810,7 @@ function App() {
<div className="history-panel">
<ChatHistory
currentConversationId={conversationId}
currentConversationTitle={conversationTitle}
currentMessages={messages}
onSelectConversation={handleSelectConversation}
onNewConversation={handleNewConversation}
Expand Down
17 changes: 10 additions & 7 deletions content-gen/src/app/frontend/src/components/ChatHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface ConversationSummary {

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

export function ChatHistory({
currentConversationId,
currentConversationTitle,
currentMessages = [],
onSelectConversation,
onNewConversation,
Expand Down Expand Up @@ -152,13 +154,14 @@ export function ChatHistory({
}, [refreshTrigger]);

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

// Merge current session with saved conversations, updating the current one with live data
const displayConversations = (() => {
Expand Down
1 change: 1 addition & 0 deletions content-gen/src/app/frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface ParsedBriefResponse {
rai_blocked?: boolean;
message: string;
conversation_id?: string;
generated_title?: string;
}

export interface GeneratedContent {
Expand Down
38 changes: 33 additions & 5 deletions content-gen/src/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from orchestrator import get_orchestrator
from services.cosmos_service import get_cosmos_service
from services.blob_service import get_blob_service
from services.title_service import get_title_service
from api.admin import admin_bp

# In-memory task storage for generation tasks
Expand Down Expand Up @@ -106,14 +107,25 @@ async def chat():
# Try to save to CosmosDB but don't fail if it's unavailable
try:
cosmos_service = await get_cosmos_service()

generated_title = None
existing_conversation = await cosmos_service.get_conversation(conversation_id, user_id)
existing_metadata = existing_conversation.get("metadata", {}) if existing_conversation else {}
has_existing_title = bool(existing_metadata.get("custom_title") or existing_metadata.get("generated_title"))

if not has_existing_title:
title_service = get_title_service()
generated_title = await title_service.generate_title(message)

await cosmos_service.add_message_to_conversation(
conversation_id=conversation_id,
user_id=user_id,
message={
"role": "user",
"content": message,
"timestamp": datetime.now(timezone.utc).isoformat()
}
},
generated_title=generated_title
)
except Exception as e:
logger.warning(f"Failed to save message to CosmosDB: {e}")
Expand Down Expand Up @@ -187,22 +199,35 @@ async def parse_brief():
if not brief_text:
return jsonify({"error": "Brief text is required"}), 400

orchestrator = get_orchestrator()
generated_title = None

# Save the user's brief text as a message to CosmosDB
try:
cosmos_service = await get_cosmos_service()

# Generate title for new conversations
existing_conversation = await cosmos_service.get_conversation(conversation_id, user_id)
existing_metadata = existing_conversation.get("metadata", {}) if existing_conversation else {}
has_existing_title = bool(existing_metadata.get("custom_title") or existing_metadata.get("generated_title"))

if not has_existing_title:
title_service = get_title_service()
generated_title = await title_service.generate_title(brief_text)

await cosmos_service.add_message_to_conversation(
conversation_id=conversation_id,
user_id=user_id,
message={
"role": "user",
"content": brief_text,
"timestamp": datetime.now(timezone.utc).isoformat()
}
},
generated_title=generated_title
)
except Exception as e:
logger.warning(f"Failed to save brief message to CosmosDB: {e}")

orchestrator = get_orchestrator()
parsed_brief, clarifying_questions, rai_blocked = await orchestrator.parse_brief(brief_text)

# Check if request was blocked due to harmful content
Expand All @@ -228,6 +253,7 @@ async def parse_brief():
"requires_clarification": False,
"requires_confirmation": False,
"conversation_id": conversation_id,
"generated_title": generated_title,
"message": clarifying_questions
})

Expand Down Expand Up @@ -255,6 +281,7 @@ async def parse_brief():
"requires_confirmation": False,
"clarifying_questions": clarifying_questions,
"conversation_id": conversation_id,
"generated_title": generated_title,
"message": clarifying_questions
})

Expand All @@ -279,6 +306,7 @@ async def parse_brief():
"requires_clarification": False,
"requires_confirmation": True,
"conversation_id": conversation_id,
"generated_title": generated_title,
"message": "Please review and confirm the parsed creative brief"
})

Expand Down Expand Up @@ -1323,12 +1351,12 @@ async def update_conversation(conversation_id: str):
async def delete_all_conversations():
"""
Delete all conversations for the current user.

Uses authenticated user from EasyAuth headers.
"""
auth_user = get_authenticated_user()
user_id = auth_user["user_principal_id"]

try:
cosmos_service = await get_cosmos_service()
deleted_count = await cosmos_service.delete_all_conversations(user_id)
Expand Down
49 changes: 38 additions & 11 deletions content-gen/src/backend/services/cosmos_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,13 +343,27 @@ async def save_conversation(
"""
await self.initialize()

# Get existing conversation to preserve important metadata fields
existing = await self.get_conversation(conversation_id, user_id)
existing_metadata = existing.get("metadata", {}) if existing else {}

# Merge metadata - preserve generated_title and custom_title from existing
merged_metadata = {}
if existing_metadata.get("generated_title"):
merged_metadata["generated_title"] = existing_metadata["generated_title"]
if existing_metadata.get("custom_title"):
merged_metadata["custom_title"] = existing_metadata["custom_title"]
# Add new metadata on top
if metadata:
merged_metadata.update(metadata)

item = {
"id": conversation_id,
"userId": user_id, # Partition key field (matches container definition /userId)
"user_id": user_id, # Keep for backward compatibility
"messages": messages,
"brief": brief.model_dump() if brief else None,
"metadata": metadata or {},
"metadata": merged_metadata,
"generated_content": generated_content,
"updated_at": datetime.now(timezone.utc).isoformat()
}
Expand Down Expand Up @@ -401,7 +415,8 @@ async def add_message_to_conversation(
self,
conversation_id: str,
user_id: str,
message: dict
message: dict,
generated_title: Optional[str] = None
) -> dict:
"""
Add a message to an existing conversation.
Expand All @@ -422,6 +437,12 @@ async def add_message_to_conversation(
# Ensure userId is set (for partition key) - migrate old documents
if not conversation.get("userId"):
conversation["userId"] = conversation.get("user_id") or user_id
conversation["metadata"] = conversation.get("metadata", {})
if generated_title:
has_custom_title = bool(conversation["metadata"].get("custom_title"))
has_generated_title = bool(conversation["metadata"].get("generated_title"))
if not has_custom_title and not has_generated_title:
conversation["metadata"]["generated_title"] = generated_title
conversation["messages"].append(message)
conversation["updated_at"] = datetime.now(timezone.utc).isoformat()
else:
Expand All @@ -430,6 +451,7 @@ async def add_message_to_conversation(
"userId": user_id, # Partition key field
"user_id": user_id, # Keep for backward compatibility
"messages": [message],
"metadata": {"generated_title": generated_title} if generated_title else {},
"updated_at": datetime.now(timezone.utc).isoformat()
}

Expand Down Expand Up @@ -494,16 +516,21 @@ async def get_user_conversations(
custom_title = metadata.get("custom_title") if metadata else None
if custom_title:
title = custom_title
elif metadata and metadata.get("generated_title"):
title = metadata.get("generated_title")
elif brief and brief.get("overview"):
title = brief["overview"][:50]
overview_words = brief["overview"].split()[:4]
title = " ".join(overview_words) if overview_words else "New Conversation"
elif messages:
title = "Untitled Conversation"
title = "New Conversation"
for msg in messages:
if msg.get("role") == "user":
title = msg.get("content", "")[:50]
content = msg.get("content", "")
words = content.split()[:4]
title = " ".join(words) if words else "New Conversation"
break
else:
title = "Untitled Conversation"
title = "New Conversation"

# Get last message preview
last_message = ""
Expand Down Expand Up @@ -597,26 +624,26 @@ async def delete_all_conversations(
) -> int:
"""
Delete all conversations for a user.

Args:
user_id: User ID to delete conversations for

Returns:
Number of conversations deleted
"""
await self.initialize()

# First get all conversations for the user
conversations = await self.get_user_conversations(user_id, limit=1000)

deleted_count = 0
for conv in conversations:
try:
await self.delete_conversation(conv["id"], user_id)
deleted_count += 1
except Exception as e:
logger.warning(f"Failed to delete conversation {conv['id']}: {e}")

logger.info(f"Deleted {deleted_count} conversations for user {user_id}")
return deleted_count

Expand Down
Loading
Loading