@hef2024/llmasaservice-ui 0.16.10 → 0.18.0
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/README.md +28 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +70 -69
- package/dist/index.mjs +70 -69
- package/docs/CONVERSATION-HISTORY.md +863 -0
- package/docs/CONVERSATION-STARTING.md +815 -0
- package/package.json +1 -1
- package/src/AIAgentPanel.css +20 -1
- package/src/AIAgentPanel.tsx +110 -23
- package/src/AIChatPanel.tsx +36 -80
- package/src/components/ui/Button.tsx +2 -0
- package/src/components/ui/Dialog.tsx +2 -0
- package/src/components/ui/Input.tsx +2 -0
- package/src/components/ui/Select.tsx +2 -0
- package/src/components/ui/Tooltip.tsx +2 -0
- package/src/components/ui/index.ts +2 -0
package/package.json
CHANGED
package/src/AIAgentPanel.css
CHANGED
|
@@ -190,9 +190,11 @@
|
|
|
190
190
|
.ai-agent-panel {
|
|
191
191
|
display: flex;
|
|
192
192
|
flex-direction: row;
|
|
193
|
+
flex-wrap: nowrap; /* Prevent sidebar from wrapping below */
|
|
193
194
|
/* Fill parent height - works in flex containers */
|
|
194
195
|
align-self: stretch;
|
|
195
196
|
height: 100%;
|
|
197
|
+
min-height: 400px; /* Fallback min-height when parent height is undefined */
|
|
196
198
|
max-height: 100%;
|
|
197
199
|
background-color: var(--ai-sidebar-bg);
|
|
198
200
|
border-left: 1px solid var(--ai-sidebar-border);
|
|
@@ -211,7 +213,7 @@
|
|
|
211
213
|
|
|
212
214
|
/* Sidebar position - left (default) */
|
|
213
215
|
.ai-agent-panel--sidebar-left {
|
|
214
|
-
flex-direction: row;
|
|
216
|
+
flex-direction: row !important;
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
.ai-agent-panel--sidebar-left .ai-agent-panel__sidebar {
|
|
@@ -1369,6 +1371,23 @@
|
|
|
1369
1371
|
border: none;
|
|
1370
1372
|
}
|
|
1371
1373
|
|
|
1374
|
+
/* --------------------------------------------------------
|
|
1375
|
+
Chat Header (for collapse button when no sidebar)
|
|
1376
|
+
-------------------------------------------------------- */
|
|
1377
|
+
.ai-agent-panel__chat-header {
|
|
1378
|
+
display: flex;
|
|
1379
|
+
align-items: center;
|
|
1380
|
+
justify-content: flex-end;
|
|
1381
|
+
padding: 8px;
|
|
1382
|
+
background-color: var(--ai-header-bg);
|
|
1383
|
+
border-bottom: 1px solid var(--ai-header-border);
|
|
1384
|
+
flex-shrink: 0;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
.ai-agent-panel__chat-header-spacer {
|
|
1388
|
+
flex: 1;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1372
1391
|
/* --------------------------------------------------------
|
|
1373
1392
|
Context Change Notification
|
|
1374
1393
|
-------------------------------------------------------- */
|
package/src/AIAgentPanel.tsx
CHANGED
|
@@ -47,6 +47,7 @@ export interface ActiveConversation {
|
|
|
47
47
|
history: Record<string, { content: string; callId: string }>;
|
|
48
48
|
isLoading: boolean;
|
|
49
49
|
title: string;
|
|
50
|
+
conversationInitialPrompt?: string; // Per-conversation initial prompt for programmatic start
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
@@ -58,6 +59,13 @@ export interface AgentConfig {
|
|
|
58
59
|
avatarUrl?: string;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Imperative handle for programmatic control of AIAgentPanel
|
|
64
|
+
*/
|
|
65
|
+
export interface AIAgentPanelHandle {
|
|
66
|
+
startNewConversation: (prompt: string, agent?: string) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
/**
|
|
62
70
|
* Props for AIAgentPanel
|
|
63
71
|
*/
|
|
@@ -83,6 +91,7 @@ export interface AIAgentPanelProps {
|
|
|
83
91
|
|
|
84
92
|
// UI Configuration
|
|
85
93
|
theme?: 'light' | 'dark';
|
|
94
|
+
collapsible?: boolean;
|
|
86
95
|
defaultCollapsed?: boolean;
|
|
87
96
|
defaultWidth?: number;
|
|
88
97
|
minWidth?: number;
|
|
@@ -120,7 +129,10 @@ export interface AIAgentPanelProps {
|
|
|
120
129
|
style?: string;
|
|
121
130
|
}[];
|
|
122
131
|
|
|
123
|
-
//
|
|
132
|
+
// Initial prompt and follow-on
|
|
133
|
+
initialPrompt?: string;
|
|
134
|
+
initialMessage?: string;
|
|
135
|
+
hideInitialPrompt?: boolean;
|
|
124
136
|
followOnQuestions?: string[];
|
|
125
137
|
followOnPrompt?: string;
|
|
126
138
|
|
|
@@ -351,6 +363,9 @@ interface ChatPanelWrapperProps {
|
|
|
351
363
|
effectiveCustomer: any;
|
|
352
364
|
showPoweredBy: boolean;
|
|
353
365
|
actions: any[];
|
|
366
|
+
initialPrompt?: string;
|
|
367
|
+
initialMessage?: string;
|
|
368
|
+
hideInitialPrompt?: boolean;
|
|
354
369
|
followOnQuestions: string[];
|
|
355
370
|
followOnPrompt: string;
|
|
356
371
|
agentOptions: any[];
|
|
@@ -364,6 +379,8 @@ interface ChatPanelWrapperProps {
|
|
|
364
379
|
enableContextDetailView: boolean;
|
|
365
380
|
// Conversation creation callback
|
|
366
381
|
onConversationCreated: (tempId: string, realId: string) => void;
|
|
382
|
+
// Per-conversation initial prompt
|
|
383
|
+
conversationInitialPrompt?: string;
|
|
367
384
|
}
|
|
368
385
|
|
|
369
386
|
// Remove React.memo temporarily to debug - ChatPanelWrapper needs to re-render when agentId changes
|
|
@@ -382,6 +399,9 @@ const ChatPanelWrapper = (({
|
|
|
382
399
|
effectiveCustomer,
|
|
383
400
|
showPoweredBy,
|
|
384
401
|
actions,
|
|
402
|
+
initialPrompt,
|
|
403
|
+
initialMessage,
|
|
404
|
+
hideInitialPrompt,
|
|
385
405
|
followOnQuestions,
|
|
386
406
|
followOnPrompt,
|
|
387
407
|
agentOptions,
|
|
@@ -393,6 +413,7 @@ const ChatPanelWrapper = (({
|
|
|
393
413
|
maxContextTokens,
|
|
394
414
|
enableContextDetailView,
|
|
395
415
|
onConversationCreated,
|
|
416
|
+
conversationInitialPrompt,
|
|
396
417
|
}) => {
|
|
397
418
|
const convAgentProfile = getAgent(activeConv.agentId);
|
|
398
419
|
const convAgentMetadata = convAgentProfile?.metadata;
|
|
@@ -441,6 +462,9 @@ const ChatPanelWrapper = (({
|
|
|
441
462
|
return convAgentProfile?.mcpServers || EMPTY_ARRAY;
|
|
442
463
|
}, [convAgentProfile?.mcpServers]);
|
|
443
464
|
|
|
465
|
+
// Determine which initialPrompt to use - conversation-specific OR prop-based
|
|
466
|
+
const effectiveInitialPrompt = conversationInitialPrompt || initialPrompt;
|
|
467
|
+
|
|
444
468
|
if (!convAgentMetadata) return null;
|
|
445
469
|
|
|
446
470
|
return (
|
|
@@ -456,17 +480,19 @@ const ChatPanelWrapper = (({
|
|
|
456
480
|
theme={theme}
|
|
457
481
|
promptTemplate={convAgentMetadata.displayPromptTemplate || '{{prompt}}'}
|
|
458
482
|
initialMessage={
|
|
459
|
-
|
|
483
|
+
initialMessage ||
|
|
484
|
+
(convAgentMetadata.displayStartMessageOrPrompt === 'message'
|
|
460
485
|
? convAgentMetadata.displayInitialMessageOrPrompt
|
|
461
|
-
: undefined
|
|
486
|
+
: undefined)
|
|
462
487
|
}
|
|
463
488
|
initialPrompt={
|
|
464
|
-
|
|
489
|
+
effectiveInitialPrompt ||
|
|
490
|
+
(convAgentMetadata.displayStartMessageOrPrompt === 'prompt'
|
|
465
491
|
? convAgentMetadata.displayInitialMessageOrPrompt
|
|
466
|
-
: undefined
|
|
492
|
+
: undefined)
|
|
467
493
|
}
|
|
468
494
|
placeholder={convAgentMetadata.displayPlaceholder || 'Type a message...'}
|
|
469
|
-
hideInitialPrompt={convAgentMetadata.displayHideInitialPrompt ?? true}
|
|
495
|
+
hideInitialPrompt={hideInitialPrompt ?? convAgentMetadata.displayHideInitialPrompt ?? true}
|
|
470
496
|
data={chatPanelData}
|
|
471
497
|
agent={activeConv.agentId}
|
|
472
498
|
conversation={activeConv.conversationId.startsWith('new-') ? undefined : activeConv.conversationId}
|
|
@@ -502,7 +528,7 @@ const ChatPanelWrapper = (({
|
|
|
502
528
|
|
|
503
529
|
ChatPanelWrapper.displayName = 'ChatPanelWrapper';
|
|
504
530
|
|
|
505
|
-
const AIAgentPanel
|
|
531
|
+
const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
506
532
|
agents,
|
|
507
533
|
defaultAgent,
|
|
508
534
|
customerId,
|
|
@@ -515,6 +541,7 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
|
|
|
515
541
|
maxContextTokens = 8000,
|
|
516
542
|
data = [],
|
|
517
543
|
theme = 'light',
|
|
544
|
+
collapsible = true,
|
|
518
545
|
defaultCollapsed = false,
|
|
519
546
|
defaultWidth = 720,
|
|
520
547
|
minWidth = 520,
|
|
@@ -534,13 +561,16 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
|
|
|
534
561
|
showPoweredBy = true,
|
|
535
562
|
conversation,
|
|
536
563
|
actions = [],
|
|
564
|
+
initialPrompt,
|
|
565
|
+
initialMessage,
|
|
566
|
+
hideInitialPrompt,
|
|
537
567
|
followOnQuestions = [],
|
|
538
568
|
followOnPrompt = '',
|
|
539
569
|
historyListLimit = 50,
|
|
540
570
|
showConversationHistory = true,
|
|
541
|
-
}) => {
|
|
542
|
-
// Panel state
|
|
543
|
-
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
|
|
571
|
+
}, ref) => {
|
|
572
|
+
// Panel state - only start collapsed if collapsible is true
|
|
573
|
+
const [isCollapsed, setIsCollapsed] = useState(collapsible && defaultCollapsed);
|
|
544
574
|
const [isHistoryCollapsed, setIsHistoryCollapsed] = useState(() => {
|
|
545
575
|
if (typeof window !== 'undefined') {
|
|
546
576
|
const saved = localStorage.getItem('ai-agent-panel-history-collapsed');
|
|
@@ -653,6 +683,33 @@ const AIAgentPanel: React.FC<AIAgentPanelProps> = ({
|
|
|
653
683
|
const currentConversationIdRef = useRef<string | null>(currentConversationId);
|
|
654
684
|
currentConversationIdRef.current = currentConversationId;
|
|
655
685
|
|
|
686
|
+
// Imperative handle for programmatic control
|
|
687
|
+
React.useImperativeHandle(ref, () => ({
|
|
688
|
+
startNewConversation: (prompt: string, agent?: string) => {
|
|
689
|
+
const targetAgent = agent || currentAgentId;
|
|
690
|
+
const tempId = `new-${Date.now()}`;
|
|
691
|
+
|
|
692
|
+
setActiveConversations(prev => {
|
|
693
|
+
const next = new Map(prev);
|
|
694
|
+
next.set(tempId, {
|
|
695
|
+
conversationId: tempId,
|
|
696
|
+
stableKey: tempId,
|
|
697
|
+
agentId: targetAgent,
|
|
698
|
+
history: {},
|
|
699
|
+
isLoading: false,
|
|
700
|
+
title: 'New conversation',
|
|
701
|
+
conversationInitialPrompt: prompt,
|
|
702
|
+
});
|
|
703
|
+
return next;
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
setCurrentConversationId(tempId);
|
|
707
|
+
if (onConversationChange) {
|
|
708
|
+
onConversationChange(tempId);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}), [currentAgentId, onConversationChange]);
|
|
712
|
+
|
|
656
713
|
// Context change notification state
|
|
657
714
|
const [showContextNotification, setShowContextNotification] = useState(false);
|
|
658
715
|
const prevContextRef = useRef<string | null>(null);
|
|
@@ -1138,15 +1195,24 @@ console.log("apiKey", apiKey);
|
|
|
1138
1195
|
// Filter by search query
|
|
1139
1196
|
if (searchQuery) {
|
|
1140
1197
|
const query = searchQuery.toLowerCase();
|
|
1141
|
-
filtered = filtered.filter(conv =>
|
|
1142
|
-
|
|
1143
|
-
conv.
|
|
1144
|
-
|
|
1198
|
+
filtered = filtered.filter(conv => {
|
|
1199
|
+
// Check title from API
|
|
1200
|
+
if (conv.title?.toLowerCase().includes(query)) return true;
|
|
1201
|
+
|
|
1202
|
+
// Check summary from API
|
|
1203
|
+
if (conv.summary?.toLowerCase().includes(query)) return true;
|
|
1204
|
+
|
|
1205
|
+
// Check the first prompt (what's actually displayed in the UI)
|
|
1206
|
+
const firstPrompt = conversationFirstPrompts[conv.conversationId];
|
|
1207
|
+
if (firstPrompt?.toLowerCase().includes(query)) return true;
|
|
1208
|
+
|
|
1209
|
+
return false;
|
|
1210
|
+
});
|
|
1145
1211
|
}
|
|
1146
1212
|
|
|
1147
1213
|
// Group all conversations, show all groups even if empty
|
|
1148
1214
|
return groupConversationsByTime(filtered, true);
|
|
1149
|
-
}, [apiConversations, searchQuery]);
|
|
1215
|
+
}, [apiConversations, searchQuery, conversationFirstPrompts]);
|
|
1150
1216
|
|
|
1151
1217
|
// Build effective customer object with required customerId
|
|
1152
1218
|
const effectiveCustomer = useMemo(() => {
|
|
@@ -1578,10 +1644,11 @@ console.log("apiKey", apiKey);
|
|
|
1578
1644
|
}
|
|
1579
1645
|
}, [onConversationChange]);
|
|
1580
1646
|
|
|
1581
|
-
// Toggle collapse
|
|
1647
|
+
// Toggle collapse - only if collapsible is enabled
|
|
1582
1648
|
const toggleCollapse = useCallback(() => {
|
|
1649
|
+
if (!collapsible) return;
|
|
1583
1650
|
setIsCollapsed((prev) => !prev);
|
|
1584
|
-
}, []);
|
|
1651
|
+
}, [collapsible]);
|
|
1585
1652
|
|
|
1586
1653
|
// Toggle history collapse
|
|
1587
1654
|
const toggleHistoryCollapse = useCallback(() => {
|
|
@@ -1602,8 +1669,8 @@ console.log("apiKey", apiKey);
|
|
|
1602
1669
|
!showConversationHistory ? 'ai-agent-panel--no-history' : '',
|
|
1603
1670
|
].filter(Boolean).join(' ');
|
|
1604
1671
|
|
|
1605
|
-
// Collapsed view
|
|
1606
|
-
if (isCollapsed) {
|
|
1672
|
+
// Collapsed view - only render if collapsible is enabled
|
|
1673
|
+
if (collapsible && isCollapsed) {
|
|
1607
1674
|
return (
|
|
1608
1675
|
<div className={panelClasses} ref={panelRef}>
|
|
1609
1676
|
<div
|
|
@@ -1878,9 +1945,11 @@ console.log("apiKey", apiKey);
|
|
|
1878
1945
|
<SidebarIcon />
|
|
1879
1946
|
</Button>
|
|
1880
1947
|
</Tooltip>
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1948
|
+
{collapsible && (
|
|
1949
|
+
<Button variant="ghost" size="icon" onClick={toggleCollapse}>
|
|
1950
|
+
{position === 'right' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
|
|
1951
|
+
</Button>
|
|
1952
|
+
)}
|
|
1884
1953
|
</div>
|
|
1885
1954
|
|
|
1886
1955
|
{/* Conversation list */}
|
|
@@ -1996,6 +2065,18 @@ console.log("apiKey", apiKey);
|
|
|
1996
2065
|
|
|
1997
2066
|
{/* Chat area */}
|
|
1998
2067
|
<div className="ai-agent-panel__chat">
|
|
2068
|
+
{/* Collapse button when no sidebar is shown */}
|
|
2069
|
+
{!showConversationHistory && collapsible && (
|
|
2070
|
+
<div className="ai-agent-panel__chat-header">
|
|
2071
|
+
<div className="ai-agent-panel__chat-header-spacer" />
|
|
2072
|
+
<Tooltip content="Collapse Panel" side={position === 'right' ? 'left' : 'right'}>
|
|
2073
|
+
<Button variant="ghost" size="icon" onClick={toggleCollapse}>
|
|
2074
|
+
{position === 'right' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
|
|
2075
|
+
</Button>
|
|
2076
|
+
</Tooltip>
|
|
2077
|
+
</div>
|
|
2078
|
+
)}
|
|
2079
|
+
|
|
1999
2080
|
{/* Context change notification */}
|
|
2000
2081
|
{showContextNotification && (
|
|
2001
2082
|
<div className="ai-agent-panel__context-notification">
|
|
@@ -2022,6 +2103,9 @@ console.log("apiKey", apiKey);
|
|
|
2022
2103
|
effectiveCustomer={effectiveCustomer}
|
|
2023
2104
|
showPoweredBy={showPoweredBy}
|
|
2024
2105
|
actions={actions}
|
|
2106
|
+
initialPrompt={initialPrompt}
|
|
2107
|
+
initialMessage={initialMessage}
|
|
2108
|
+
hideInitialPrompt={hideInitialPrompt}
|
|
2025
2109
|
followOnQuestions={followOnQuestions}
|
|
2026
2110
|
followOnPrompt={followOnPrompt}
|
|
2027
2111
|
agentOptions={agentOptions}
|
|
@@ -2033,6 +2117,7 @@ console.log("apiKey", apiKey);
|
|
|
2033
2117
|
maxContextTokens={maxContextTokens}
|
|
2034
2118
|
enableContextDetailView={enableContextDetailView}
|
|
2035
2119
|
onConversationCreated={handleConversationCreated}
|
|
2120
|
+
conversationInitialPrompt={activeConv.conversationInitialPrompt}
|
|
2036
2121
|
/>
|
|
2037
2122
|
))}
|
|
2038
2123
|
|
|
@@ -2090,7 +2175,9 @@ console.log("apiKey", apiKey);
|
|
|
2090
2175
|
</Dialog>
|
|
2091
2176
|
</div>
|
|
2092
2177
|
);
|
|
2093
|
-
};
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
AIAgentPanel.displayName = 'AIAgentPanel';
|
|
2094
2181
|
|
|
2095
2182
|
export default AIAgentPanel;
|
|
2096
2183
|
|
package/src/AIChatPanel.tsx
CHANGED
|
@@ -703,6 +703,8 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
703
703
|
const hasNotifiedCompletionRef = useRef<boolean>(true);
|
|
704
704
|
// Store the latest processed history for callbacks (doesn't trigger re-renders)
|
|
705
705
|
const latestHistoryRef = useRef<Record<string, HistoryEntry>>(initialHistory);
|
|
706
|
+
// Track if we've sent the initial prompt (prevents loops)
|
|
707
|
+
const initialPromptSentRef = useRef<boolean>(false);
|
|
706
708
|
|
|
707
709
|
// Sync new entries from initialHistory into local history state
|
|
708
710
|
// This allows parent components to inject messages (e.g., page-based agent suggestions)
|
|
@@ -1063,13 +1065,39 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1063
1065
|
|
|
1064
1066
|
setIsLoading(true);
|
|
1065
1067
|
|
|
1068
|
+
// === OPTIMISTIC UPDATE: Show prompt immediately in UI ===
|
|
1069
|
+
// Generate unique key using ISO timestamp prefix + prompt
|
|
1070
|
+
const timestamp = new Date().toISOString();
|
|
1071
|
+
const promptKey = `${timestamp}:${promptToSend.trim()}`;
|
|
1072
|
+
|
|
1073
|
+
// Add prompt to history IMMEDIATELY - this makes it appear in the UI right away
|
|
1074
|
+
setHistory((prevHistory) => ({
|
|
1075
|
+
...prevHistory,
|
|
1076
|
+
[promptKey]: { content: '', callId: '' },
|
|
1077
|
+
}));
|
|
1078
|
+
|
|
1079
|
+
// Store the key for later use
|
|
1080
|
+
setLastPrompt(promptToSend.trim());
|
|
1081
|
+
setLastKey(promptKey);
|
|
1082
|
+
|
|
1083
|
+
// Scroll to bottom immediately to show the new prompt
|
|
1084
|
+
// Use setTimeout to ensure the DOM has updated
|
|
1085
|
+
setTimeout(() => {
|
|
1086
|
+
scrollToBottom();
|
|
1087
|
+
}, 0);
|
|
1088
|
+
|
|
1089
|
+
// Now proceed with API calls in the background (conversation creation + LLM call)
|
|
1066
1090
|
// Ensure conversation exists before sending (matches ChatPanel)
|
|
1067
1091
|
console.log('AIChatPanel.continueChat - about to call ensureConversation');
|
|
1068
1092
|
ensureConversation().then((convId) => {
|
|
1069
1093
|
console.log('AIChatPanel.continueChat - ensureConversation resolved with:', convId);
|
|
1070
1094
|
// Build messagesAndHistory from history (matches ChatPanel)
|
|
1095
|
+
// IMPORTANT: Exclude the current prompt (promptKey) since it's new and we're sending it now
|
|
1071
1096
|
const messagesAndHistory: { role: string; content: string }[] = [];
|
|
1072
1097
|
Object.entries(history).forEach(([historyPrompt, historyEntry]) => {
|
|
1098
|
+
// Skip the current prompt we just added optimistically
|
|
1099
|
+
if (historyPrompt === promptKey) return;
|
|
1100
|
+
|
|
1073
1101
|
// Strip timestamp prefix from prompt before using it (matches ChatPanel)
|
|
1074
1102
|
let promptForHistory = historyPrompt;
|
|
1075
1103
|
const isoTimestampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z:/;
|
|
@@ -1085,19 +1113,10 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1085
1113
|
messagesAndHistory.push({ role: 'assistant', content: historyEntry.content });
|
|
1086
1114
|
});
|
|
1087
1115
|
|
|
1088
|
-
// Generate unique key using ISO timestamp prefix + prompt (matches ChatPanel)
|
|
1089
|
-
const timestamp = new Date().toISOString();
|
|
1090
|
-
const promptKey = `${timestamp}:${promptToSend.trim()}`;
|
|
1091
|
-
|
|
1092
|
-
// Set history entry before sending (matches ChatPanel)
|
|
1093
|
-
setHistory((prevHistory) => ({
|
|
1094
|
-
...prevHistory,
|
|
1095
|
-
[promptKey]: { content: '', callId: '' },
|
|
1096
|
-
}));
|
|
1097
|
-
|
|
1098
1116
|
// Build the full prompt - only apply template for first message (matches ChatPanel)
|
|
1117
|
+
// Check if this is the first message by seeing if messagesAndHistory is empty
|
|
1099
1118
|
let fullPromptToSend = promptToSend.trim();
|
|
1100
|
-
if (
|
|
1119
|
+
if (messagesAndHistory.length === 0 && promptTemplate) {
|
|
1101
1120
|
fullPromptToSend = promptTemplate.replace('{{prompt}}', fullPromptToSend);
|
|
1102
1121
|
}
|
|
1103
1122
|
|
|
@@ -1126,9 +1145,7 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1126
1145
|
newController
|
|
1127
1146
|
);
|
|
1128
1147
|
|
|
1129
|
-
setLastPrompt(promptToSend.trim());
|
|
1130
1148
|
setLastMessages(messagesAndHistory);
|
|
1131
|
-
setLastKey(promptKey);
|
|
1132
1149
|
|
|
1133
1150
|
// Notify parent of new conversation ID AFTER send() has started
|
|
1134
1151
|
// This prevents the component from being remounted before send() runs
|
|
@@ -1138,12 +1155,6 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1138
1155
|
onConversationCreated(convId);
|
|
1139
1156
|
}, 100);
|
|
1140
1157
|
}
|
|
1141
|
-
|
|
1142
|
-
// Scroll to bottom after adding the new prompt to show it immediately
|
|
1143
|
-
// Use setTimeout to ensure the DOM has updated with the new history entry
|
|
1144
|
-
setTimeout(() => {
|
|
1145
|
-
scrollToBottom();
|
|
1146
|
-
}, 0);
|
|
1147
1158
|
});
|
|
1148
1159
|
}, [
|
|
1149
1160
|
idle,
|
|
@@ -1376,69 +1387,14 @@ const AIChatPanel: React.FC<AIChatPanelProps> = ({
|
|
|
1376
1387
|
};
|
|
1377
1388
|
}, []); // Empty deps - only run cleanup on unmount
|
|
1378
1389
|
|
|
1379
|
-
// Auto-send initialPrompt
|
|
1380
|
-
//
|
|
1390
|
+
// Auto-send initialPrompt exactly once when component mounts
|
|
1391
|
+
// Simple behavior matching original ChatPanel - just send the prompt
|
|
1381
1392
|
useEffect(() => {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
// Don't proceed if project_id is not yet available
|
|
1387
|
-
if (!project_id) {
|
|
1388
|
-
return;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
if (initialPrompt && initialPrompt !== '' && initialPrompt !== lastPrompt && !hasLoadedHistory) {
|
|
1392
|
-
setIsLoading(true);
|
|
1393
|
-
setThinkingBlocks([]);
|
|
1394
|
-
setCurrentThinkingIndex(0);
|
|
1395
|
-
setUserHasScrolled(false); // Enable auto-scroll for new prompt
|
|
1396
|
-
|
|
1397
|
-
// Ensure conversation exists before sending (matches ChatPanel)
|
|
1398
|
-
ensureConversation().then((convId) => {
|
|
1399
|
-
const controller = new AbortController();
|
|
1400
|
-
setLastController(controller);
|
|
1401
|
-
|
|
1402
|
-
// Generate timestamp-prefixed key (matches ChatPanel)
|
|
1403
|
-
const timestamp = new Date().toISOString();
|
|
1404
|
-
const promptKey = `${timestamp}:${initialPrompt}`;
|
|
1405
|
-
|
|
1406
|
-
// Set history entry before sending (matches ChatPanel)
|
|
1407
|
-
setHistory({ [promptKey]: { content: '', callId: '' } });
|
|
1408
|
-
|
|
1409
|
-
// Build prompt with template
|
|
1410
|
-
let fullPrompt = initialPrompt;
|
|
1411
|
-
if (promptTemplate) {
|
|
1412
|
-
fullPrompt = promptTemplate.replace('{{prompt}}', initialPrompt);
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
send(
|
|
1416
|
-
fullPrompt,
|
|
1417
|
-
[],
|
|
1418
|
-
[
|
|
1419
|
-
...dataWithExtras(),
|
|
1420
|
-
{ key: '--messages', data: '0' },
|
|
1421
|
-
],
|
|
1422
|
-
true,
|
|
1423
|
-
true,
|
|
1424
|
-
service,
|
|
1425
|
-
convId, // Use conversation ID from ensureConversation
|
|
1426
|
-
controller
|
|
1427
|
-
);
|
|
1428
|
-
|
|
1429
|
-
setLastPrompt(initialPrompt);
|
|
1430
|
-
setLastMessages([]);
|
|
1431
|
-
setLastKey(promptKey);
|
|
1432
|
-
|
|
1433
|
-
// Notify parent of new conversation ID AFTER send() has started
|
|
1434
|
-
if (convId && onConversationCreated) {
|
|
1435
|
-
setTimeout(() => {
|
|
1436
|
-
onConversationCreated(convId);
|
|
1437
|
-
}, 100);
|
|
1438
|
-
}
|
|
1439
|
-
});
|
|
1393
|
+
if (initialPrompt && initialPrompt !== '' && !initialPromptSentRef.current) {
|
|
1394
|
+
initialPromptSentRef.current = true;
|
|
1395
|
+
continueChat(initialPrompt);
|
|
1440
1396
|
}
|
|
1441
|
-
}, [initialPrompt,
|
|
1397
|
+
}, [initialPrompt, continueChat]);
|
|
1442
1398
|
|
|
1443
1399
|
// ============================================================================
|
|
1444
1400
|
// Render Helpers
|