@hef2024/llmasaservice-ui 0.24.4 → 0.24.6
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/dist/index.css +24 -0
- package/dist/index.d.mts +27 -14
- package/dist/index.d.ts +27 -14
- package/dist/index.js +442 -132
- package/dist/index.mjs +442 -132
- package/index.ts +8 -1
- package/package.json +1 -1
- package/src/AIAgentPanel.tsx +213 -41
- package/src/AIChatPanel.css +14 -0
- package/src/AIChatPanel.tsx +333 -129
- package/src/ChatPanel.css +15 -1
- package/src/ChatPanel.tsx +45 -38
- package/src/components/ui/ThinkingBlock.tsx +25 -1
package/index.ts
CHANGED
|
@@ -23,7 +23,14 @@ export type {
|
|
|
23
23
|
// Import and export AIChatPanel (shadcn-style chat interface)
|
|
24
24
|
import AIChatPanel from './src/AIChatPanel';
|
|
25
25
|
export { AIChatPanel };
|
|
26
|
-
export type {
|
|
26
|
+
export type {
|
|
27
|
+
AIChatPanelProps,
|
|
28
|
+
AIChatHistoryEntry,
|
|
29
|
+
ArtifactBlockType,
|
|
30
|
+
PlanningStep,
|
|
31
|
+
PlanningStepStatus,
|
|
32
|
+
ResponseArtifactBlock,
|
|
33
|
+
} from './src/AIChatPanel';
|
|
27
34
|
|
|
28
35
|
// Export hooks
|
|
29
36
|
export { useAgentRegistry } from './src/hooks/useAgentRegistry';
|
package/package.json
CHANGED
package/src/AIAgentPanel.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { LLMAsAServiceCustomer } from 'llmasaservice-client';
|
|
|
2
2
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import './AIAgentPanel.css';
|
|
4
4
|
import AIChatPanel, {
|
|
5
|
+
AIChatHistoryEntry,
|
|
5
6
|
AgentOption,
|
|
6
7
|
BeforeSendPayload,
|
|
7
8
|
LocalToolExecutor,
|
|
@@ -63,7 +64,7 @@ export interface ActiveConversation {
|
|
|
63
64
|
conversationId: string;
|
|
64
65
|
stableKey: string; // Stable key for React component - never changes
|
|
65
66
|
agentId: string;
|
|
66
|
-
history: Record<string,
|
|
67
|
+
history: Record<string, AIChatHistoryEntry>;
|
|
67
68
|
transcriptLoaded: boolean; // Prevent re-fetch loops for conversations with empty history
|
|
68
69
|
isLoading: boolean;
|
|
69
70
|
title: string;
|
|
@@ -134,7 +135,7 @@ export interface AIAgentPanelProps {
|
|
|
134
135
|
onAgentSwitch?: (fromAgent: string, toAgent: string) => void;
|
|
135
136
|
onConversationChange?: (conversationId: string) => void;
|
|
136
137
|
onBeforeSend?: (payload: BeforeSendPayload) => Promise<void> | void;
|
|
137
|
-
historyChangedCallback?: (history: Record<string,
|
|
138
|
+
historyChangedCallback?: (history: Record<string, AIChatHistoryEntry>) => void;
|
|
138
139
|
responseCompleteCallback?: (callId: string, prompt: string, response: string) => void;
|
|
139
140
|
thumbsUpClick?: (callId: string) => void;
|
|
140
141
|
thumbsDownClick?: (callId: string) => void;
|
|
@@ -708,8 +709,8 @@ const buildTranscriptTurnsFromCalls = (calls: Record<string, unknown>[]): Transc
|
|
|
708
709
|
|
|
709
710
|
const buildHistoryFromTranscriptTurns = (
|
|
710
711
|
turns: TranscriptTurn[],
|
|
711
|
-
): Record<string,
|
|
712
|
-
const history: Record<string,
|
|
712
|
+
): Record<string, AIChatHistoryEntry> => {
|
|
713
|
+
const history: Record<string, AIChatHistoryEntry> = {};
|
|
713
714
|
const keyUsageByPrompt = new Map<string, number>();
|
|
714
715
|
|
|
715
716
|
turns.forEach((turn, index) => {
|
|
@@ -740,9 +741,86 @@ const truncatePromptForTitle = (prompt: string): string => {
|
|
|
740
741
|
return prompt;
|
|
741
742
|
};
|
|
742
743
|
|
|
744
|
+
const stripHistoryPromptTimestamp = (prompt: string): string => {
|
|
745
|
+
const isoMatch = prompt.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z:(.+)/);
|
|
746
|
+
if (isoMatch && isoMatch[1]) {
|
|
747
|
+
return isoMatch[1];
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return prompt.replace(/^\d+:/, '');
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const upsertConversationSummary = (
|
|
754
|
+
conversations: APIConversationSummary[],
|
|
755
|
+
summary: APIConversationSummary,
|
|
756
|
+
): APIConversationSummary[] => {
|
|
757
|
+
const normalizedSummary = normalizeConversationListPayload([summary])[0];
|
|
758
|
+
if (!normalizedSummary) {
|
|
759
|
+
return conversations;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const dedupedById = new Map<string, APIConversationSummary>();
|
|
763
|
+
for (const conv of conversations) {
|
|
764
|
+
dedupedById.set(conv.conversationId, conv);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const existing = dedupedById.get(normalizedSummary.conversationId);
|
|
768
|
+
const existingUpdatedAt = existing ? new Date(existing.updatedAt).getTime() : 0;
|
|
769
|
+
const incomingUpdatedAt = new Date(normalizedSummary.updatedAt).getTime();
|
|
770
|
+
const preferIncoming = !existing || incomingUpdatedAt >= existingUpdatedAt;
|
|
771
|
+
const latest = preferIncoming ? normalizedSummary : existing;
|
|
772
|
+
const fallback = preferIncoming ? existing : normalizedSummary;
|
|
773
|
+
|
|
774
|
+
dedupedById.set(normalizedSummary.conversationId, {
|
|
775
|
+
...(fallback || {}),
|
|
776
|
+
...(latest || {}),
|
|
777
|
+
createdAt: existing?.createdAt || normalizedSummary.createdAt,
|
|
778
|
+
updatedAt: latest?.updatedAt || fallback?.updatedAt || normalizedSummary.updatedAt,
|
|
779
|
+
title: latest?.title || fallback?.title || '',
|
|
780
|
+
summary: latest?.summary || fallback?.summary,
|
|
781
|
+
agentId: latest?.agentId || fallback?.agentId,
|
|
782
|
+
messageCount:
|
|
783
|
+
typeof latest?.messageCount === 'number'
|
|
784
|
+
? latest.messageCount
|
|
785
|
+
: fallback?.messageCount,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
return Array.from(dedupedById.values()).sort(
|
|
789
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
790
|
+
);
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const buildOptimisticConversationSummary = (
|
|
794
|
+
conversation: ActiveConversation,
|
|
795
|
+
): APIConversationSummary | null => {
|
|
796
|
+
const conversationId =
|
|
797
|
+
typeof conversation.conversationId === 'string' ? conversation.conversationId.trim() : '';
|
|
798
|
+
if (!conversationId || conversationId.startsWith('new-')) {
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const historyKeys = Object.keys(conversation.history || {});
|
|
803
|
+
if (historyKeys.length === 0 && conversation.title === 'New conversation') {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const firstPrompt = historyKeys[0] ? stripHistoryPromptTimestamp(historyKeys[0]) : '';
|
|
808
|
+
const title = truncatePromptForTitle(firstPrompt || conversation.title || 'New conversation');
|
|
809
|
+
const now = new Date().toISOString();
|
|
810
|
+
|
|
811
|
+
return {
|
|
812
|
+
conversationId,
|
|
813
|
+
title,
|
|
814
|
+
createdAt: now,
|
|
815
|
+
updatedAt: now,
|
|
816
|
+
agentId: conversation.agentId,
|
|
817
|
+
messageCount: historyKeys.length > 0 ? historyKeys.length * 2 : undefined,
|
|
818
|
+
};
|
|
819
|
+
};
|
|
820
|
+
|
|
743
821
|
// Empty arrays/objects to prevent recreating on every render
|
|
744
822
|
const EMPTY_ARRAY: never[] = [];
|
|
745
|
-
const EMPTY_HISTORY: Record<string,
|
|
823
|
+
const EMPTY_HISTORY: Record<string, AIChatHistoryEntry> = {};
|
|
746
824
|
|
|
747
825
|
/**
|
|
748
826
|
* Memoized wrapper for AIChatPanel to prevent unnecessary re-renders
|
|
@@ -754,7 +832,7 @@ interface ChatPanelWrapperProps {
|
|
|
754
832
|
url: string;
|
|
755
833
|
theme: 'light' | 'dark';
|
|
756
834
|
chatPanelData: { key: string; data: string }[];
|
|
757
|
-
handleHistoryChanged: (history: Record<string,
|
|
835
|
+
handleHistoryChanged: (history: Record<string, AIChatHistoryEntry>, conversationId?: string) => void;
|
|
758
836
|
handleLoadingChange: (isLoading: boolean, conversationId?: string) => void;
|
|
759
837
|
responseCompleteCallback?: (callId: string, prompt: string, response: string) => void;
|
|
760
838
|
thumbsUpClick?: (callId: string) => void;
|
|
@@ -867,7 +945,7 @@ const ChatPanelWrapper = (({
|
|
|
867
945
|
|
|
868
946
|
// Memoize callbacks to prevent recreating on every render
|
|
869
947
|
const historyCallback = useCallback(
|
|
870
|
-
(history: Record<string,
|
|
948
|
+
(history: Record<string, AIChatHistoryEntry>) => {
|
|
871
949
|
handleHistoryChanged(history, activeConv.conversationId);
|
|
872
950
|
},
|
|
873
951
|
[handleHistoryChanged, activeConv.conversationId]
|
|
@@ -1308,12 +1386,15 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1308
1386
|
agentList,
|
|
1309
1387
|
} = useAgentRegistry(agentIds, { url, localOverrides });
|
|
1310
1388
|
|
|
1389
|
+
const getAgentRef = useRef(getAgent);
|
|
1390
|
+
useEffect(() => {
|
|
1391
|
+
getAgentRef.current = getAgent;
|
|
1392
|
+
}, [getAgent]);
|
|
1393
|
+
|
|
1311
1394
|
// Ref to track if fetch is in progress (prevents duplicate calls)
|
|
1312
1395
|
const fetchInProgressRef = useRef(false);
|
|
1313
1396
|
const loadingTranscriptIdsRef = useRef<Set<string>>(new Set());
|
|
1314
|
-
|
|
1315
|
-
// Ref to track the last agent we fetched for
|
|
1316
|
-
const lastFetchedAgentRef = useRef<string | null>(null);
|
|
1397
|
+
const [conversationListRefreshNonce, setConversationListRefreshNonce] = useState(0);
|
|
1317
1398
|
|
|
1318
1399
|
// Ref to track which conversations we've checked for prompts
|
|
1319
1400
|
const checkedPromptsRef = useRef<Set<string>>(new Set());
|
|
@@ -1332,6 +1413,43 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1332
1413
|
const currentConversationIdRef = useRef<string | null>(currentConversationId);
|
|
1333
1414
|
currentConversationIdRef.current = currentConversationId;
|
|
1334
1415
|
|
|
1416
|
+
const controlledConversationIdRef = useRef<string | null>(controlledConversationId);
|
|
1417
|
+
controlledConversationIdRef.current = controlledConversationId;
|
|
1418
|
+
|
|
1419
|
+
const requestConversationListRefresh = useCallback(() => {
|
|
1420
|
+
setConversationListRefreshNonce(prev => prev + 1);
|
|
1421
|
+
}, []);
|
|
1422
|
+
|
|
1423
|
+
const upsertApiConversationFromHistory = useCallback(
|
|
1424
|
+
(
|
|
1425
|
+
conversationId: string,
|
|
1426
|
+
history: Record<string, AIChatHistoryEntry>,
|
|
1427
|
+
fallbackTitle?: string,
|
|
1428
|
+
) => {
|
|
1429
|
+
const firstPromptKey = Object.keys(history)[0];
|
|
1430
|
+
const firstPrompt = firstPromptKey ? stripHistoryPromptTimestamp(firstPromptKey) : '';
|
|
1431
|
+
const title = truncatePromptForTitle(firstPrompt || fallbackTitle || 'New conversation');
|
|
1432
|
+
const messageCount = Object.keys(history).length * 2;
|
|
1433
|
+
const timestamp = new Date().toISOString();
|
|
1434
|
+
|
|
1435
|
+
setApiConversations(prev =>
|
|
1436
|
+
upsertConversationSummary(prev, {
|
|
1437
|
+
conversationId,
|
|
1438
|
+
title,
|
|
1439
|
+
createdAt:
|
|
1440
|
+
prev.find((conversation) => conversation.conversationId === conversationId)?.createdAt ||
|
|
1441
|
+
timestamp,
|
|
1442
|
+
updatedAt: timestamp,
|
|
1443
|
+
agentId:
|
|
1444
|
+
prev.find((conversation) => conversation.conversationId === conversationId)?.agentId ||
|
|
1445
|
+
currentAgentId,
|
|
1446
|
+
messageCount,
|
|
1447
|
+
}),
|
|
1448
|
+
);
|
|
1449
|
+
},
|
|
1450
|
+
[currentAgentId]
|
|
1451
|
+
);
|
|
1452
|
+
|
|
1335
1453
|
// Centralized conversation selection flow.
|
|
1336
1454
|
// In controlled mode, we only update local selection for prop-driven updates (notify=false).
|
|
1337
1455
|
const commitConversationSelection = useCallback(
|
|
@@ -1433,7 +1551,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1433
1551
|
|
|
1434
1552
|
// Use the conversation's agentId if available, otherwise fall back to currentAgentId
|
|
1435
1553
|
const agentIdToUse = agentIdForConversation || currentAgentId;
|
|
1436
|
-
const agentProfile =
|
|
1554
|
+
const agentProfile = getAgentRef.current(agentIdToUse);
|
|
1437
1555
|
const projectId = agentProfile?.metadata?.projectId;
|
|
1438
1556
|
|
|
1439
1557
|
if (!apiKey || !projectId) {
|
|
@@ -1522,14 +1640,14 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1522
1640
|
return next;
|
|
1523
1641
|
});
|
|
1524
1642
|
}
|
|
1525
|
-
}, [apiKey, currentAgentId
|
|
1643
|
+
}, [apiKey, currentAgentId]);
|
|
1526
1644
|
|
|
1527
1645
|
// Fetch conversations from API
|
|
1528
|
-
const fetchConversations = useCallback(async (
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1646
|
+
const fetchConversations = useCallback(async (
|
|
1647
|
+
agentId: string,
|
|
1648
|
+
projectId: string,
|
|
1649
|
+
signal?: AbortSignal,
|
|
1650
|
+
) => {
|
|
1533
1651
|
if (!agentId || !apiKey || !projectId) {
|
|
1534
1652
|
setApiConversations([]);
|
|
1535
1653
|
return;
|
|
@@ -1563,7 +1681,44 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1563
1681
|
const normalized = normalizeConversationListPayload(payload);
|
|
1564
1682
|
|
|
1565
1683
|
if (!signal?.aborted) {
|
|
1566
|
-
setApiConversations(
|
|
1684
|
+
setApiConversations(prev => {
|
|
1685
|
+
const next = [...normalized];
|
|
1686
|
+
const optimisticIds = new Set<string>();
|
|
1687
|
+
|
|
1688
|
+
const trackedConversationId = controlledConversationIdRef.current;
|
|
1689
|
+
if (trackedConversationId) {
|
|
1690
|
+
optimisticIds.add(trackedConversationId);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
activeConversationsRef.current.forEach((conversation) => {
|
|
1694
|
+
const optimisticSummary = buildOptimisticConversationSummary(conversation);
|
|
1695
|
+
if (!optimisticSummary) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
optimisticIds.add(optimisticSummary.conversationId);
|
|
1700
|
+
const existing = prev.find(
|
|
1701
|
+
(candidate) => candidate.conversationId === optimisticSummary.conversationId
|
|
1702
|
+
);
|
|
1703
|
+
next.push({
|
|
1704
|
+
...optimisticSummary,
|
|
1705
|
+
createdAt: existing?.createdAt || optimisticSummary.createdAt,
|
|
1706
|
+
updatedAt: optimisticSummary.updatedAt,
|
|
1707
|
+
});
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
let merged = normalizeConversationListPayload(next);
|
|
1711
|
+
if (optimisticIds.size > 0) {
|
|
1712
|
+
prev.forEach((conversation) => {
|
|
1713
|
+
if (!optimisticIds.has(conversation.conversationId)) {
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
merged = upsertConversationSummary(merged, conversation);
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
return merged;
|
|
1721
|
+
});
|
|
1567
1722
|
|
|
1568
1723
|
// Fetch first prompts for Today and Yesterday conversations
|
|
1569
1724
|
const now = new Date();
|
|
@@ -1588,7 +1743,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1588
1743
|
setConversationsLoading(false);
|
|
1589
1744
|
}
|
|
1590
1745
|
}
|
|
1591
|
-
}, [apiKey, customerId,
|
|
1746
|
+
}, [apiKey, customerId, fetchFirstPrompt]);
|
|
1592
1747
|
|
|
1593
1748
|
// Helper to strip context/template data from a prompt - keeps only the user's actual message
|
|
1594
1749
|
// Load a specific conversation's transcript (or switch to it if already active)
|
|
@@ -1616,7 +1771,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1616
1771
|
|
|
1617
1772
|
// Use the conversation's agentId if available, otherwise fall back to currentAgentId
|
|
1618
1773
|
const agentIdToUse = agentIdForConversation || currentAgentId;
|
|
1619
|
-
const agentProfile =
|
|
1774
|
+
const agentProfile = getAgentRef.current(agentIdToUse);
|
|
1620
1775
|
const projectId = agentProfile?.metadata?.projectId;
|
|
1621
1776
|
|
|
1622
1777
|
if (!apiKey || !projectId) {
|
|
@@ -1711,7 +1866,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1711
1866
|
loadingTranscriptIdsRef.current.delete(conversationId);
|
|
1712
1867
|
setLoadingConversationId(prev => (prev === conversationId ? null : prev));
|
|
1713
1868
|
}
|
|
1714
|
-
}, [apiKey, commitConversationSelection, conversationFirstPrompts, currentAgentId
|
|
1869
|
+
}, [apiKey, commitConversationSelection, conversationFirstPrompts, currentAgentId]);
|
|
1715
1870
|
|
|
1716
1871
|
// Controlled conversation mode: keep panel selection in sync with external conversation prop.
|
|
1717
1872
|
useEffect(() => {
|
|
@@ -1798,10 +1953,15 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1798
1953
|
]);
|
|
1799
1954
|
|
|
1800
1955
|
|
|
1956
|
+
const currentAgentProjectId = useMemo(() => {
|
|
1957
|
+
const agentProfile = getAgent(currentAgentId);
|
|
1958
|
+
return agentProfile?.metadata?.projectId || '';
|
|
1959
|
+
}, [currentAgentId, getAgent]);
|
|
1960
|
+
|
|
1801
1961
|
// Refresh conversations callback
|
|
1802
1962
|
const handleRefreshConversations = useCallback(() => {
|
|
1803
|
-
|
|
1804
|
-
}, [
|
|
1963
|
+
requestConversationListRefresh();
|
|
1964
|
+
}, [requestConversationListRefresh]);
|
|
1805
1965
|
|
|
1806
1966
|
// Fetch conversations on mount and when agent changes
|
|
1807
1967
|
useEffect(() => {
|
|
@@ -1810,17 +1970,21 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
1810
1970
|
|
|
1811
1971
|
// Only fetch if agents are loaded and we have necessary data
|
|
1812
1972
|
if (!agentsLoading && currentAgentId && apiKey) {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
// Only fetch once per agent, and only if agent is ready
|
|
1818
|
-
if (projectId && lastFetchedAgentRef.current !== currentAgentId) {
|
|
1819
|
-
lastFetchedAgentRef.current = currentAgentId;
|
|
1820
|
-
fetchConversations(currentAgentId);
|
|
1973
|
+
if (currentAgentProjectId) {
|
|
1974
|
+
const controller = new AbortController();
|
|
1975
|
+
fetchConversations(currentAgentId, currentAgentProjectId, controller.signal);
|
|
1976
|
+
return () => controller.abort();
|
|
1821
1977
|
}
|
|
1822
1978
|
}
|
|
1823
|
-
}, [
|
|
1979
|
+
}, [
|
|
1980
|
+
agentsLoading,
|
|
1981
|
+
apiKey,
|
|
1982
|
+
conversationListRefreshNonce,
|
|
1983
|
+
currentAgentId,
|
|
1984
|
+
currentAgentProjectId,
|
|
1985
|
+
fetchConversations,
|
|
1986
|
+
showConversationHistory,
|
|
1987
|
+
]);
|
|
1824
1988
|
|
|
1825
1989
|
// Start new conversation
|
|
1826
1990
|
const handleNewConversation = useCallback(() => {
|
|
@@ -2273,7 +2437,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
2273
2437
|
|
|
2274
2438
|
// Handle history changes from ChatPanel
|
|
2275
2439
|
const handleHistoryChanged = useCallback(
|
|
2276
|
-
(history: Record<string,
|
|
2440
|
+
(history: Record<string, AIChatHistoryEntry>, conversationId?: string) => {
|
|
2277
2441
|
const targetConversationId = conversationId || currentConversationIdRef.current;
|
|
2278
2442
|
|
|
2279
2443
|
// Update active conversation history
|
|
@@ -2287,12 +2451,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
2287
2451
|
if (title === 'New conversation' && Object.keys(history).length > 0) {
|
|
2288
2452
|
const firstPrompt = Object.keys(history)[0];
|
|
2289
2453
|
if (firstPrompt) {
|
|
2290
|
-
|
|
2291
|
-
let cleanPrompt = firstPrompt;
|
|
2292
|
-
const isoMatch = cleanPrompt.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z:(.+)/);
|
|
2293
|
-
if (isoMatch && isoMatch[1]) {
|
|
2294
|
-
cleanPrompt = isoMatch[1];
|
|
2295
|
-
}
|
|
2454
|
+
const cleanPrompt = stripHistoryPromptTimestamp(firstPrompt);
|
|
2296
2455
|
title = cleanPrompt.length > 60 ? cleanPrompt.slice(0, 57) + '...' : cleanPrompt;
|
|
2297
2456
|
}
|
|
2298
2457
|
}
|
|
@@ -2306,6 +2465,13 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
2306
2465
|
}
|
|
2307
2466
|
return prev;
|
|
2308
2467
|
});
|
|
2468
|
+
|
|
2469
|
+
const existingActiveConversation = activeConversationsRef.current.get(targetConversationId);
|
|
2470
|
+
upsertApiConversationFromHistory(
|
|
2471
|
+
targetConversationId,
|
|
2472
|
+
history,
|
|
2473
|
+
existingActiveConversation?.title,
|
|
2474
|
+
);
|
|
2309
2475
|
}
|
|
2310
2476
|
|
|
2311
2477
|
// Check for agent handoff suggestion
|
|
@@ -2328,9 +2494,11 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
2328
2494
|
}
|
|
2329
2495
|
},
|
|
2330
2496
|
[
|
|
2497
|
+
activeConversationsRef,
|
|
2331
2498
|
currentAgentId,
|
|
2332
2499
|
historyChangedCallback,
|
|
2333
2500
|
agents,
|
|
2501
|
+
upsertApiConversationFromHistory,
|
|
2334
2502
|
]
|
|
2335
2503
|
);
|
|
2336
2504
|
|
|
@@ -2342,6 +2510,9 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
2342
2510
|
setActiveConversations(prev => {
|
|
2343
2511
|
const existing = prev.get(targetConversationId);
|
|
2344
2512
|
if (existing && existing.isLoading !== isLoading) {
|
|
2513
|
+
if (existing.isLoading && !isLoading) {
|
|
2514
|
+
requestConversationListRefresh();
|
|
2515
|
+
}
|
|
2345
2516
|
const next = new Map(prev);
|
|
2346
2517
|
next.set(targetConversationId, {
|
|
2347
2518
|
...existing,
|
|
@@ -2352,7 +2523,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
2352
2523
|
return prev;
|
|
2353
2524
|
});
|
|
2354
2525
|
}
|
|
2355
|
-
}, []);
|
|
2526
|
+
}, [requestConversationListRefresh]);
|
|
2356
2527
|
|
|
2357
2528
|
// Handle handoff confirmation
|
|
2358
2529
|
const handleHandoffConfirm = useCallback(() => {
|
|
@@ -2413,7 +2584,8 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
|
|
|
2413
2584
|
if (currentConversationIdRef.current === tempId) {
|
|
2414
2585
|
commitConversationSelection(realId, true);
|
|
2415
2586
|
}
|
|
2416
|
-
|
|
2587
|
+
requestConversationListRefresh();
|
|
2588
|
+
}, [commitConversationSelection, requestConversationListRefresh]);
|
|
2417
2589
|
|
|
2418
2590
|
// Toggle collapse - only if collapsible is enabled
|
|
2419
2591
|
const toggleCollapse = useCallback(() => {
|
package/src/AIChatPanel.css
CHANGED
|
@@ -2700,6 +2700,14 @@
|
|
|
2700
2700
|
border-color: var(--thinking-block-accent);
|
|
2701
2701
|
}
|
|
2702
2702
|
|
|
2703
|
+
.thinking-block--planning {
|
|
2704
|
+
--thinking-block-accent: #f59e0b;
|
|
2705
|
+
--thinking-block-bg: #fffbeb;
|
|
2706
|
+
--thinking-block-text: #b45309;
|
|
2707
|
+
background-color: var(--thinking-block-bg);
|
|
2708
|
+
border-color: var(--thinking-block-accent);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2703
2711
|
/* Dark theme type-specific */
|
|
2704
2712
|
.dark-theme .thinking-block--thinking {
|
|
2705
2713
|
--thinking-block-bg: #1e1b2e;
|
|
@@ -2719,6 +2727,12 @@
|
|
|
2719
2727
|
--thinking-block-text: #86efac;
|
|
2720
2728
|
}
|
|
2721
2729
|
|
|
2730
|
+
.dark-theme .thinking-block--planning {
|
|
2731
|
+
--thinking-block-bg: #2b2111;
|
|
2732
|
+
--thinking-block-accent: #fbbf24;
|
|
2733
|
+
--thinking-block-text: #fcd34d;
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2722
2736
|
/* Header (clickable toggle) */
|
|
2723
2737
|
.thinking-block__header {
|
|
2724
2738
|
display: flex;
|