@hef2024/llmasaservice-ui 0.16.8 → 0.16.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hef2024/llmasaservice-ui",
3
- "version": "0.16.8",
3
+ "version": "0.16.9",
4
4
  "description": "Prebuilt UI components for LLMAsAService.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -569,13 +569,18 @@
569
569
  }
570
570
 
571
571
  .ai-agent-panel__loading {
572
+ position: absolute;
573
+ top: 0;
574
+ left: 0;
575
+ right: 0;
576
+ bottom: 0;
572
577
  display: flex;
573
578
  flex-direction: column;
574
579
  align-items: center;
575
580
  justify-content: center;
576
- flex: 1;
577
581
  gap: 12px;
578
582
  color: var(--ai-sidebar-text-muted);
583
+ background-color: var(--ai-chat-bg);
579
584
  }
580
585
 
581
586
  .ai-agent-panel__conversation-loading-overlay {
@@ -1352,3 +1357,76 @@
1352
1357
  border-left: 1px solid var(--ai-sidebar-border);
1353
1358
  }
1354
1359
 
1360
+ /* --------------------------------------------------------
1361
+ No History Mode - Hide sidebar completely
1362
+ -------------------------------------------------------- */
1363
+ .ai-agent-panel--no-history .ai-agent-panel__sidebar {
1364
+ display: none;
1365
+ }
1366
+
1367
+ .ai-agent-panel--no-history .ai-agent-panel__chat {
1368
+ flex: 1;
1369
+ border: none;
1370
+ }
1371
+
1372
+ /* --------------------------------------------------------
1373
+ Context Change Notification
1374
+ -------------------------------------------------------- */
1375
+ .ai-agent-panel__context-notification {
1376
+ position: absolute;
1377
+ bottom: 70px;
1378
+ left: 50%;
1379
+ transform: translateX(-50%);
1380
+ z-index: 50;
1381
+ display: flex;
1382
+ align-items: center;
1383
+ gap: 8px;
1384
+ padding: 8px 16px;
1385
+ background-color: var(--ai-agent-badge-bg);
1386
+ color: var(--ai-agent-badge-text);
1387
+ border-radius: 20px;
1388
+ font-size: 13px;
1389
+ font-weight: 500;
1390
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
1391
+ animation: ai-context-notification-in 0.3s ease-out, ai-context-notification-out 0.3s ease-in 2.7s forwards;
1392
+ pointer-events: none;
1393
+ }
1394
+
1395
+ .ai-agent-panel__context-notification svg {
1396
+ flex-shrink: 0;
1397
+ animation: ai-sparkle-pulse 0.6s ease-in-out infinite alternate;
1398
+ }
1399
+
1400
+ @keyframes ai-context-notification-in {
1401
+ 0% {
1402
+ opacity: 0;
1403
+ transform: translateX(-50%) translateY(10px) scale(0.9);
1404
+ }
1405
+ 100% {
1406
+ opacity: 1;
1407
+ transform: translateX(-50%) translateY(0) scale(1);
1408
+ }
1409
+ }
1410
+
1411
+ @keyframes ai-context-notification-out {
1412
+ 0% {
1413
+ opacity: 1;
1414
+ transform: translateX(-50%) translateY(0) scale(1);
1415
+ }
1416
+ 100% {
1417
+ opacity: 0;
1418
+ transform: translateX(-50%) translateY(10px) scale(0.9);
1419
+ }
1420
+ }
1421
+
1422
+ @keyframes ai-sparkle-pulse {
1423
+ 0% {
1424
+ opacity: 0.7;
1425
+ transform: scale(0.9);
1426
+ }
1427
+ 100% {
1428
+ opacity: 1;
1429
+ transform: scale(1.1);
1430
+ }
1431
+ }
1432
+
@@ -1,9 +1,9 @@
1
- import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
2
- import AIChatPanel, { AgentOption } from './AIChatPanel';
3
- import { useAgentRegistry } from './hooks/useAgentRegistry';
4
- import { Button, Input, ScrollArea, Dialog, DialogFooter, Tooltip } from './components/ui';
5
1
  import { LLMAsAServiceCustomer } from 'llmasaservice-client';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
3
  import './AIAgentPanel.css';
4
+ import AIChatPanel, { AgentOption } from './AIChatPanel';
5
+ import { Button, Dialog, DialogFooter, Input, ScrollArea, Tooltip } from './components/ui';
6
+ import { useAgentRegistry } from './hooks/useAgentRegistry';
7
7
 
8
8
  /**
9
9
  * Context section for agent awareness
@@ -42,6 +42,7 @@ export interface APIConversationSummary {
42
42
  */
43
43
  export interface ActiveConversation {
44
44
  conversationId: string;
45
+ stableKey: string; // Stable key for React component - never changes
45
46
  agentId: string;
46
47
  history: Record<string, { content: string; callId: string }>;
47
48
  isLoading: boolean;
@@ -125,6 +126,9 @@ export interface AIAgentPanelProps {
125
126
 
126
127
  // Conversation history settings
127
128
  historyListLimit?: number;
129
+
130
+ // Enable/disable conversation history panel
131
+ showConversationHistory?: boolean;
128
132
  }
129
133
 
130
134
  // Icons
@@ -193,6 +197,13 @@ const SidebarIcon = () => (
193
197
  </svg>
194
198
  );
195
199
 
200
+ const SparkleIcon = () => (
201
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
202
+ <path d="M8 1v3M8 12v3M3 8H0M16 8h-3M12.95 3.05l-2.12 2.12M5.17 10.83l-2.12 2.12M12.95 12.95l-2.12-2.12M5.17 5.17L3.05 3.05" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
203
+ <circle cx="8" cy="8" r="2" fill="currentColor"/>
204
+ </svg>
205
+ );
206
+
196
207
  /**
197
208
  * AIAgentPanel - Cursor-inspired multi-agent panel
198
209
  */
@@ -351,6 +362,8 @@ interface ChatPanelWrapperProps {
351
362
  totalContextTokens: number;
352
363
  maxContextTokens: number;
353
364
  enableContextDetailView: boolean;
365
+ // Conversation creation callback
366
+ onConversationCreated: (tempId: string, realId: string) => void;
354
367
  }
355
368
 
356
369
  // Remove React.memo temporarily to debug - ChatPanelWrapper needs to re-render when agentId changes
@@ -379,6 +392,7 @@ const ChatPanelWrapper = (({
379
392
  totalContextTokens,
380
393
  maxContextTokens,
381
394
  enableContextDetailView,
395
+ onConversationCreated,
382
396
  }) => {
383
397
  const convAgentProfile = getAgent(activeConv.agentId);
384
398
  const convAgentMetadata = convAgentProfile?.metadata;
@@ -399,6 +413,14 @@ const ChatPanelWrapper = (({
399
413
  [handleLoadingChange, activeConv.conversationId]
400
414
  );
401
415
 
416
+ // Callback when AIChatPanel creates a new conversation via API
417
+ const conversationCreatedCallback = useCallback(
418
+ (realConversationId: string) => {
419
+ onConversationCreated(activeConv.conversationId, realConversationId);
420
+ },
421
+ [onConversationCreated, activeConv.conversationId]
422
+ );
423
+
402
424
  // Compute follow-on questions - MUST update when agent switches
403
425
  // Don't use useMemo - compute fresh every render to ensure it always reflects current agent
404
426
  // The computation is cheap (just string split), and this guarantees updates
@@ -472,6 +494,7 @@ const ChatPanelWrapper = (({
472
494
  totalContextTokens={totalContextTokens}
473
495
  maxContextTokens={maxContextTokens}
474
496
  enableContextDetailView={enableContextDetailView}
497
+ onConversationCreated={conversationCreatedCallback}
475
498
  />
476
499
  </div>
477
500
  );
@@ -514,6 +537,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
514
537
  followOnQuestions = [],
515
538
  followOnPrompt = '',
516
539
  historyListLimit = 50,
540
+ showConversationHistory = true,
517
541
  }) => {
518
542
  // Panel state
519
543
  const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
@@ -540,6 +564,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
540
564
  const [showSearch, setShowSearch] = useState(false);
541
565
  const [showHandoffDialog, setShowHandoffDialog] = useState(false);
542
566
  const [suggestedAgent, setSuggestedAgent] = useState<string | null>(null);
567
+ const [handoffSource, setHandoffSource] = useState<'agent' | 'page'>('agent');
543
568
  const panelRef = useRef<HTMLDivElement>(null);
544
569
  const resizeRef = useRef<HTMLDivElement>(null);
545
570
 
@@ -628,6 +653,14 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
628
653
  const currentConversationIdRef = useRef<string | null>(currentConversationId);
629
654
  currentConversationIdRef.current = currentConversationId;
630
655
 
656
+ // Context change notification state
657
+ const [showContextNotification, setShowContextNotification] = useState(false);
658
+ const prevContextRef = useRef<string | null>(null);
659
+ const contextNotificationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
660
+
661
+ // Track previous defaultAgent to avoid re-suggesting the same agent
662
+ const prevDefaultAgentRef = useRef<string | null>(null);
663
+
631
664
  // Fetch first prompt from a conversation
632
665
  const fetchFirstPrompt = useCallback(async (conversationId: string, agentIdForConversation?: string) => {
633
666
  // Skip if already checked/loaded
@@ -774,6 +807,9 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
774
807
 
775
808
  setConversationsLoading(true);
776
809
  setConversationsError(null);
810
+ console.log("projectId", projectId);
811
+ console.log("customerId", customerId);
812
+ console.log("apiKey", apiKey);
777
813
 
778
814
  try {
779
815
  console.log('fetchConversations - customerId:', customerId);
@@ -942,6 +978,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
942
978
  const next = new Map(prev);
943
979
  next.set(conversationId, {
944
980
  conversationId,
981
+ stableKey: conversationId, // Use real ID as stable key when loading from API
945
982
  agentId: agentIdToUse,
946
983
  history,
947
984
  isLoading: false,
@@ -975,6 +1012,9 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
975
1012
 
976
1013
  // Fetch conversations on mount and when agent changes
977
1014
  useEffect(() => {
1015
+ // Skip fetching if conversation history is disabled
1016
+ if (!showConversationHistory) return;
1017
+
978
1018
  // Only fetch if agents are loaded and we have necessary data
979
1019
  if (!agentsLoading && currentAgentId && apiKey) {
980
1020
  // Check if agent is ready (has projectId)
@@ -987,7 +1027,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
987
1027
  fetchConversations(currentAgentId);
988
1028
  }
989
1029
  }
990
- }, [agentsLoading, currentAgentId, apiKey, fetchConversations, getAgent]);
1030
+ }, [agentsLoading, currentAgentId, apiKey, fetchConversations, getAgent, showConversationHistory]);
991
1031
 
992
1032
  // Start new conversation
993
1033
  const handleNewConversation = useCallback(() => {
@@ -999,6 +1039,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
999
1039
  const next = new Map(prev);
1000
1040
  next.set(tempId, {
1001
1041
  conversationId: tempId,
1042
+ stableKey: tempId, // Stable key never changes even when conversationId updates
1002
1043
  agentId: currentAgentId,
1003
1044
  history: {},
1004
1045
  isLoading: false,
@@ -1202,6 +1243,125 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1202
1243
  }, [context, sharedContextSections, pageContextSections, contextDataSources]);
1203
1244
 
1204
1245
 
1246
+ // Detect context changes and show notification
1247
+ useEffect(() => {
1248
+ // Create a stable string representation of the context for comparison
1249
+ const contextString = JSON.stringify(mergedContext.sections.map(s => ({ id: s.id, data: s.data })));
1250
+
1251
+ // Skip on first render
1252
+ if (prevContextRef.current === null) {
1253
+ prevContextRef.current = contextString;
1254
+ return;
1255
+ }
1256
+
1257
+ // Check if context actually changed
1258
+ if (prevContextRef.current !== contextString) {
1259
+ console.log('AIAgentPanel - Context changed, showing notification');
1260
+ prevContextRef.current = contextString;
1261
+
1262
+ // Clear any existing timeout before setting a new one
1263
+ if (contextNotificationTimeoutRef.current) {
1264
+ clearTimeout(contextNotificationTimeoutRef.current);
1265
+ contextNotificationTimeoutRef.current = null;
1266
+ }
1267
+
1268
+ // Show the notification
1269
+ setShowContextNotification(true);
1270
+
1271
+ // Auto-hide after 3 seconds
1272
+ contextNotificationTimeoutRef.current = setTimeout(() => {
1273
+ setShowContextNotification(false);
1274
+ contextNotificationTimeoutRef.current = null;
1275
+ }, 3000);
1276
+ }
1277
+
1278
+ // Note: No cleanup on every re-render - only clear timeout when context changes
1279
+ // This prevents race conditions when switching rapidly
1280
+ }, [mergedContext.sections]);
1281
+
1282
+ // Separate cleanup effect for unmount only
1283
+ useEffect(() => {
1284
+ return () => {
1285
+ if (contextNotificationTimeoutRef.current) {
1286
+ clearTimeout(contextNotificationTimeoutRef.current);
1287
+ }
1288
+ };
1289
+ }, []);
1290
+
1291
+ // Auto-suggest agent switch when page context includes a defaultAgent
1292
+ // Injects a synthetic message with [SUGGEST_AGENT:...] into the chat history
1293
+ useEffect(() => {
1294
+ // Look for a defaultAgent in any page context section
1295
+ let foundDefaultAgent: string | null = null;
1296
+
1297
+ for (const section of pageContextSections) {
1298
+ if (section.data && typeof section.data === 'object' && 'defaultAgent' in section.data) {
1299
+ const defaultAgentValue = section.data.defaultAgent;
1300
+ if (typeof defaultAgentValue === 'string' && defaultAgentValue) {
1301
+ foundDefaultAgent = defaultAgentValue;
1302
+ break;
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ // Skip if no defaultAgent found or it's the same as last time we processed
1308
+ if (!foundDefaultAgent || foundDefaultAgent === prevDefaultAgentRef.current) {
1309
+ return;
1310
+ }
1311
+
1312
+ // Skip if it's already the current agent
1313
+ if (foundDefaultAgent === currentAgentId) {
1314
+ // Update ref so we don't re-process if user switches away and back
1315
+ prevDefaultAgentRef.current = foundDefaultAgent;
1316
+ return;
1317
+ }
1318
+
1319
+ // Check if this agent is in our available agents list and is ready
1320
+ const isAvailable = agentIds.includes(foundDefaultAgent);
1321
+ const agentProfile = getAgent(foundDefaultAgent);
1322
+ const isReady = agentProfile?.status === 'ready';
1323
+
1324
+ if (isAvailable && isReady && currentConversationIdRef.current) {
1325
+ // Get the agent's display name
1326
+ const agentName = agentProfile?.metadata?.displayTitle ||
1327
+ localOverrides[foundDefaultAgent]?.localName ||
1328
+ foundDefaultAgent;
1329
+
1330
+ // Create a synthetic message with the SUGGEST_AGENT marker
1331
+ // This will be processed by the existing agent suggestion card rendering
1332
+ const suggestionMessage = `This page recommends a different agent for better assistance.\n\n[SUGGEST_AGENT:${foundDefaultAgent}|${agentName}|Recommended for this page]`;
1333
+
1334
+ // Create a unique key for the synthetic message (includes timestamp to allow multiple suggestions)
1335
+ // Using __system__: prefix tells AIChatPanel to hide the "user message" part
1336
+ const syntheticKey = `__system__:page_suggestion_${foundDefaultAgent}_${Date.now()}`;
1337
+
1338
+ // Update ref BEFORE injecting to prevent duplicate processing
1339
+ prevDefaultAgentRef.current = foundDefaultAgent;
1340
+
1341
+ // Inject the synthetic message into the current conversation's history
1342
+ setActiveConversations(prev => {
1343
+ const conversationId = currentConversationIdRef.current;
1344
+ if (!conversationId) return prev;
1345
+
1346
+ const existing = prev.get(conversationId);
1347
+ if (!existing) return prev;
1348
+
1349
+ const next = new Map(prev);
1350
+ next.set(conversationId, {
1351
+ ...existing,
1352
+ history: {
1353
+ ...existing.history,
1354
+ [syntheticKey]: {
1355
+ content: suggestionMessage,
1356
+ callId: `page-suggestion-${Date.now()}`,
1357
+ },
1358
+ },
1359
+ });
1360
+ return next;
1361
+ });
1362
+ }
1363
+ }, [pageContextSections, currentAgentId, agentIds, getAgent, localOverrides]);
1364
+
1205
1365
  // Build data array for ChatPanel
1206
1366
  const chatPanelData = useMemo(() => {
1207
1367
  const contextData = mergedContext.sections.map((section) => ({
@@ -1337,6 +1497,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1337
1497
  const suggestedId = handoffMatch[1];
1338
1498
  if (suggestedId && suggestedId !== currentAgentId && agents.includes(suggestedId)) {
1339
1499
  setSuggestedAgent(suggestedId);
1500
+ setHandoffSource('agent');
1340
1501
  setShowHandoffDialog(true);
1341
1502
  }
1342
1503
  }
@@ -1380,13 +1541,43 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1380
1541
  }
1381
1542
  setShowHandoffDialog(false);
1382
1543
  setSuggestedAgent(null);
1544
+ setHandoffSource('agent');
1383
1545
  }, [suggestedAgent, handleAgentSwitch]);
1384
1546
 
1385
1547
  const handleHandoffCancel = useCallback(() => {
1386
1548
  setShowHandoffDialog(false);
1387
1549
  setSuggestedAgent(null);
1550
+ setHandoffSource('agent');
1388
1551
  }, []);
1389
1552
 
1553
+ // Handle conversation created - update temp ID to real ID from API
1554
+ const handleConversationCreated = useCallback((tempId: string, realId: string) => {
1555
+ console.log('Conversation created:', tempId, '->', realId);
1556
+
1557
+ // Update activeConversations - replace temp ID with real ID
1558
+ setActiveConversations(prev => {
1559
+ const existing = prev.get(tempId);
1560
+ if (existing) {
1561
+ const next = new Map(prev);
1562
+ next.delete(tempId);
1563
+ next.set(realId, {
1564
+ ...existing,
1565
+ conversationId: realId,
1566
+ });
1567
+ return next;
1568
+ }
1569
+ return prev;
1570
+ });
1571
+
1572
+ // Update currentConversationId if it was the temp one
1573
+ if (currentConversationIdRef.current === tempId) {
1574
+ setCurrentConversationId(realId);
1575
+ if (onConversationChange) {
1576
+ onConversationChange(realId);
1577
+ }
1578
+ }
1579
+ }, [onConversationChange]);
1580
+
1390
1581
  // Toggle collapse
1391
1582
  const toggleCollapse = useCallback(() => {
1392
1583
  setIsCollapsed((prev) => !prev);
@@ -1408,6 +1599,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1408
1599
  isCollapsed ? 'ai-agent-panel--collapsed' : '',
1409
1600
  position === 'left' ? 'ai-agent-panel--left' : 'ai-agent-panel--right',
1410
1601
  sidebarPosition === 'right' ? 'ai-agent-panel--sidebar-right' : 'ai-agent-panel--sidebar-left',
1602
+ !showConversationHistory ? 'ai-agent-panel--no-history' : '',
1411
1603
  ].filter(Boolean).join(' ');
1412
1604
 
1413
1605
  // Collapsed view
@@ -1433,18 +1625,20 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1433
1625
 
1434
1626
  <div className="ai-agent-panel__collapsed-divider" />
1435
1627
 
1436
- <Tooltip content="Search" side="left">
1437
- <Button
1438
- variant="ghost"
1439
- size="icon"
1440
- onClick={() => {
1441
- setIsCollapsed(false);
1442
- setShowSearch(true);
1443
- }}
1444
- >
1445
- <SearchIcon />
1446
- </Button>
1447
- </Tooltip>
1628
+ {showConversationHistory && (
1629
+ <Tooltip content="Search" side="left">
1630
+ <Button
1631
+ variant="ghost"
1632
+ size="icon"
1633
+ onClick={() => {
1634
+ setIsCollapsed(false);
1635
+ setShowSearch(true);
1636
+ }}
1637
+ >
1638
+ <SearchIcon />
1639
+ </Button>
1640
+ </Tooltip>
1641
+ )}
1448
1642
 
1449
1643
  <Tooltip content="New Chat" side="left">
1450
1644
  <Button
@@ -1489,6 +1683,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1489
1683
  const next = new Map(prev);
1490
1684
  next.set(tempId, {
1491
1685
  conversationId: tempId,
1686
+ stableKey: tempId, // Stable key never changes
1492
1687
  agentId: agent.id,
1493
1688
  history: {},
1494
1689
  isLoading: false,
@@ -1543,7 +1738,8 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1543
1738
  tabIndex={0}
1544
1739
  />
1545
1740
 
1546
- {/* Sidebar */}
1741
+ {/* Sidebar - only show if conversation history is enabled */}
1742
+ {showConversationHistory && (
1547
1743
  <div className={`ai-agent-panel__sidebar ${isHistoryCollapsed ? 'ai-agent-panel__sidebar--collapsed' : ''}`}>
1548
1744
  {isHistoryCollapsed ? (
1549
1745
  // Collapsed history bar
@@ -1609,6 +1805,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1609
1805
  const next = new Map(prev);
1610
1806
  next.set(tempId, {
1611
1807
  conversationId: tempId,
1808
+ stableKey: tempId, // Stable key never changes
1612
1809
  agentId: agent.id,
1613
1810
  history: {},
1614
1811
  isLoading: false,
@@ -1696,7 +1893,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1696
1893
  </div>
1697
1894
  {activeConversationsList.map((activeConv) => (
1698
1895
  <div
1699
- key={activeConv.conversationId}
1896
+ key={activeConv.stableKey}
1700
1897
  className={`ai-agent-panel__conversation ai-agent-panel__conversation--active-item ${
1701
1898
  currentConversationId === activeConv.conversationId
1702
1899
  ? 'ai-agent-panel__conversation--current'
@@ -1795,13 +1992,22 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1795
1992
  </>
1796
1993
  )}
1797
1994
  </div>
1995
+ )}
1798
1996
 
1799
1997
  {/* Chat area */}
1800
1998
  <div className="ai-agent-panel__chat">
1999
+ {/* Context change notification */}
2000
+ {showContextNotification && (
2001
+ <div className="ai-agent-panel__context-notification">
2002
+ <SparkleIcon />
2003
+ <span>Agent now has new context</span>
2004
+ </div>
2005
+ )}
2006
+
1801
2007
  {/* Chat panels - one per active conversation, shown/hidden via CSS */}
1802
2008
  {activeConversationsList.map((activeConv) => (
1803
2009
  <ChatPanelWrapper
1804
- key={`${activeConv.conversationId}-${activeConv.agentId}`}
2010
+ key={`${activeConv.stableKey}-${activeConv.agentId}`}
1805
2011
  activeConv={activeConv}
1806
2012
  currentConversationId={currentConversationId}
1807
2013
  getAgent={getAgent}
@@ -1826,6 +2032,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1826
2032
  totalContextTokens={mergedContext.totalTokens || 0}
1827
2033
  maxContextTokens={maxContextTokens}
1828
2034
  enableContextDetailView={enableContextDetailView}
2035
+ onConversationCreated={handleConversationCreated}
1829
2036
  />
1830
2037
  ))}
1831
2038
 
@@ -1862,9 +2069,15 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
1862
2069
  isOpen={showHandoffDialog}
1863
2070
  onClose={handleHandoffCancel}
1864
2071
  title="Switch Agent?"
1865
- description={`The current agent suggests transferring this conversation to ${
1866
- suggestedAgent ? getAgent(suggestedAgent)?.metadata?.displayTitle || suggestedAgent : 'another agent'
1867
- }.`}
2072
+ description={
2073
+ handoffSource === 'page'
2074
+ ? `This page has a recommended agent: ${
2075
+ suggestedAgent ? getAgent(suggestedAgent)?.metadata?.displayTitle || suggestedAgent : 'another agent'
2076
+ }. Would you like to switch?`
2077
+ : `The current agent suggests transferring this conversation to ${
2078
+ suggestedAgent ? getAgent(suggestedAgent)?.metadata?.displayTitle || suggestedAgent : 'another agent'
2079
+ }.`
2080
+ }
1868
2081
  >
1869
2082
  <DialogFooter>
1870
2083
  <Button variant="outline" onClick={handleHandoffCancel}>