@hef2024/llmasaservice-ui 0.25.2 → 0.26.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/index.ts CHANGED
@@ -26,6 +26,11 @@ export { AIChatPanel };
26
26
  export type {
27
27
  AIChatPanelProps,
28
28
  AIChatHistoryEntry,
29
+ ComposerAgentModeControl,
30
+ CompactContextInput,
31
+ CompactContextResult,
32
+ CompactionAction,
33
+ CompactionTokenUsage,
29
34
  ArtifactBlockType,
30
35
  PlanningStep,
31
36
  PlanningStepStatus,
package/package.json CHANGED
@@ -1,10 +1,21 @@
1
1
  {
2
2
  "name": "@hef2024/llmasaservice-ui",
3
- "version": "0.25.2",
3
+ "version": "0.26.0",
4
4
  "description": "Prebuilt UI components for LLMAsAService.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
+ "style": "dist/index.css",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./dist/index.css": "./dist/index.css",
17
+ "./package.json": "./package.json"
18
+ },
8
19
  "scripts": {
9
20
  "build": "tsup index.ts --format cjs,esm --dts",
10
21
  "lint": "tsc",
@@ -109,6 +109,7 @@
109
109
  /* Resize Handle */
110
110
  --ai-resize-handle-width: 4px;
111
111
  --ai-resize-handle-color: transparent;
112
+ --ai-resize-handle-idle: rgba(100, 100, 100, 0.15);
112
113
  --ai-resize-handle-hover: #3b82f6;
113
114
 
114
115
  /* Chat Area */
@@ -270,7 +271,7 @@
270
271
 
271
272
  /* Subtle hint on parent hover to show resize is available */
272
273
  .ai-agent-panel:hover .ai-agent-panel__resize-handle {
273
- background-color: rgba(100, 100, 100, 0.15);
274
+ background-color: var(--ai-resize-handle-idle);
274
275
  }
275
276
 
276
277
  /* Position resize handle based on panel position */
@@ -497,6 +498,16 @@
497
498
  line-height: 1.5;
498
499
  }
499
500
 
501
+ .ai-agent-panel__conversation-subtitle {
502
+ margin-top: 2px;
503
+ font-size: 11px;
504
+ line-height: 1.25;
505
+ color: var(--ai-conversation-meta-color);
506
+ white-space: nowrap;
507
+ overflow: hidden;
508
+ text-overflow: ellipsis;
509
+ }
510
+
500
511
  .ai-agent-panel__conversation-preview {
501
512
  font-size: 11px;
502
513
  color: var(--ai-conversation-preview-color);
@@ -1209,6 +1220,10 @@
1209
1220
  background-color: rgba(59, 130, 246, 0.08) !important;
1210
1221
  }
1211
1222
 
1223
+ .ai-agent-panel__conversation--just-focused {
1224
+ box-shadow: inset 0 0 0 1px rgba(139, 92, 246, 0.35);
1225
+ }
1226
+
1212
1227
  .ai-agent-panel__conversation--in-active {
1213
1228
  opacity: 0.7;
1214
1229
  }
@@ -1453,4 +1468,3 @@
1453
1468
  transform: scale(1.1);
1454
1469
  }
1455
1470
  }
1456
-
@@ -5,6 +5,9 @@ import AIChatPanel, {
5
5
  AIChatHistoryEntry,
6
6
  AgentOption,
7
7
  BeforeSendPayload,
8
+ ComposerAgentModeControl,
9
+ CompactContextInput,
10
+ CompactContextResult,
8
11
  LocalToolExecutor,
9
12
  ToolStatusLabelFormatter,
10
13
  TraceContextMode,
@@ -136,6 +139,8 @@ export interface AIAgentPanelProps {
136
139
  onAgentSwitch?: (fromAgent: string, toAgent: string) => void;
137
140
  onConversationChange?: (conversationId: string) => void;
138
141
  onBeforeSend?: (payload: BeforeSendPayload) => Promise<void> | void;
142
+ compactContext?: (input: CompactContextInput) => Promise<CompactContextResult> | CompactContextResult;
143
+ compactionPreserveTurns?: number;
139
144
  historyChangedCallback?: (history: Record<string, AIChatHistoryEntry>) => void;
140
145
  responseCompleteCallback?: (callId: string, prompt: string, response: string) => void;
141
146
  thumbsUpClick?: (callId: string) => void;
@@ -166,6 +171,10 @@ export interface AIAgentPanelProps {
166
171
 
167
172
  // Conversation history settings
168
173
  historyListLimit?: number;
174
+ conversationSubtitleResolver?: (
175
+ conversationId: string,
176
+ conversation?: APIConversationSummary | ActiveConversation,
177
+ ) => string | null | undefined;
169
178
 
170
179
  // Enable/disable conversation history panel
171
180
  showConversationHistory?: boolean;
@@ -191,6 +200,7 @@ export interface AIAgentPanelProps {
191
200
  traceContextMode?: TraceContextMode;
192
201
  autoApproveTools?: boolean | string[];
193
202
  toolStatusLabelFormatter?: ToolStatusLabelFormatter;
203
+ composerAgentModeControl?: ComposerAgentModeControl;
194
204
  }
195
205
 
196
206
  // Icons
@@ -283,6 +293,10 @@ const normalizeConversationListPayload = (payload: any): APIConversationSummary[
283
293
  }
284
294
 
285
295
  const resolvedConversationId =
296
+ conv.sessionId ||
297
+ conv.session_id ||
298
+ conv.sessionKey ||
299
+ conv.session_key ||
286
300
  conv.conversationId ||
287
301
  conv.id ||
288
302
  conv.conversation_id ||
@@ -947,6 +961,8 @@ interface ChatPanelWrapperProps {
947
961
  // Conversation creation callback
948
962
  onConversationCreated: (tempId: string, realId: string) => void;
949
963
  onBeforeSend?: (payload: BeforeSendPayload) => Promise<void> | void;
964
+ compactContext?: (input: CompactContextInput) => Promise<CompactContextResult> | CompactContextResult;
965
+ compactionPreserveTurns?: number;
950
966
  // Per-conversation initial prompt
951
967
  conversationInitialPrompt?: string;
952
968
  // New props from ChatPanel port
@@ -970,6 +986,7 @@ interface ChatPanelWrapperProps {
970
986
  traceContextMode?: TraceContextMode;
971
987
  autoApproveTools?: boolean | string[];
972
988
  toolStatusLabelFormatter?: ToolStatusLabelFormatter;
989
+ composerAgentModeControl?: ComposerAgentModeControl;
973
990
  }
974
991
 
975
992
  // Remove React.memo temporarily to debug - ChatPanelWrapper needs to re-render when agentId changes
@@ -1005,6 +1022,8 @@ const ChatPanelWrapper = (({
1005
1022
  onToggleSection,
1006
1023
  onConversationCreated,
1007
1024
  onBeforeSend,
1025
+ compactContext,
1026
+ compactionPreserveTurns,
1008
1027
  conversationInitialPrompt,
1009
1028
  // New props from ChatPanel port
1010
1029
  cssUrl,
@@ -1027,6 +1046,7 @@ const ChatPanelWrapper = (({
1027
1046
  traceContextMode,
1028
1047
  autoApproveTools,
1029
1048
  toolStatusLabelFormatter,
1049
+ composerAgentModeControl,
1030
1050
  }) => {
1031
1051
  const convAgentProfile = getAgent(activeConv.agentId);
1032
1052
  const convAgentMetadata = convAgentProfile?.metadata;
@@ -1150,6 +1170,8 @@ const ChatPanelWrapper = (({
1150
1170
  onToggleSection={onToggleSection}
1151
1171
  onConversationCreated={conversationCreatedCallback}
1152
1172
  onBeforeSend={beforeSendCallback}
1173
+ compactContext={compactContext}
1174
+ compactionPreserveTurns={compactionPreserveTurns}
1153
1175
  cssUrl={cssUrl}
1154
1176
  markdownClass={markdownClass}
1155
1177
  width={width}
@@ -1170,6 +1192,7 @@ const ChatPanelWrapper = (({
1170
1192
  traceContextMode={traceContextMode}
1171
1193
  autoApproveTools={autoApproveTools}
1172
1194
  toolStatusLabelFormatter={toolStatusLabelFormatter}
1195
+ composerAgentModeControl={composerAgentModeControl}
1173
1196
  />
1174
1197
  </div>
1175
1198
  );
@@ -1229,6 +1252,8 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1229
1252
  onAgentSwitch,
1230
1253
  onConversationChange,
1231
1254
  onBeforeSend,
1255
+ compactContext,
1256
+ compactionPreserveTurns,
1232
1257
  historyChangedCallback,
1233
1258
  responseCompleteCallback,
1234
1259
  thumbsUpClick,
@@ -1244,6 +1269,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1244
1269
  followOnQuestions = [],
1245
1270
  followOnPrompt = '',
1246
1271
  historyListLimit = 50,
1272
+ conversationSubtitleResolver,
1247
1273
  showConversationHistory = true,
1248
1274
  // New props from ChatPanel port
1249
1275
  cssUrl,
@@ -1266,6 +1292,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1266
1292
  traceContextMode = 'standard',
1267
1293
  autoApproveTools,
1268
1294
  toolStatusLabelFormatter,
1295
+ composerAgentModeControl,
1269
1296
  }, ref) => {
1270
1297
  // Dev mode warnings for prop conflicts
1271
1298
  useEffect(() => {
@@ -1465,6 +1492,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
1465
1492
  'This Month': false,
1466
1493
  'Older': false,
1467
1494
  });
1495
+ const [recentlyFocusedConversationId, setRecentlyFocusedConversationId] = useState<string | null>(null);
1468
1496
 
1469
1497
  // Context section toggle state (per-conversation disabled sections)
1470
1498
  const [disabledContextSections, setDisabledContextSections] = useState<Map<string, Set<string>>>(new Map());
@@ -2000,11 +2028,14 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
2000
2028
  return;
2001
2029
  }
2002
2030
 
2031
+ if (currentConversationIdRef.current !== targetConversationId) {
2032
+ setCurrentConversationId(targetConversationId);
2033
+ }
2034
+
2003
2035
  const existingActive = activeConversationsRef.current.get(targetConversationId);
2004
2036
  if (
2005
2037
  existingActive &&
2006
- existingActive.transcriptLoaded &&
2007
- currentConversationIdRef.current === targetConversationId
2038
+ existingActive.transcriptLoaded
2008
2039
  ) {
2009
2040
  return;
2010
2041
  }
@@ -2196,6 +2227,57 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
2196
2227
  return groupConversationsByTime(filtered, true);
2197
2228
  }, [apiConversations, searchQuery, conversationFirstPrompts]);
2198
2229
 
2230
+ // Ensure the selected conversation is visible in history after controlled route/session changes.
2231
+ useEffect(() => {
2232
+ if (!currentConversationId) {
2233
+ return;
2234
+ }
2235
+
2236
+ const matchingGroup = groupedConversations.find((group) =>
2237
+ group.conversations.some((conversation) => conversation.conversationId === currentConversationId)
2238
+ );
2239
+ if (!matchingGroup) {
2240
+ return;
2241
+ }
2242
+
2243
+ setExpandedSections((prev) => {
2244
+ if (prev[matchingGroup.label]) {
2245
+ return prev;
2246
+ }
2247
+ return {
2248
+ ...prev,
2249
+ [matchingGroup.label]: true,
2250
+ };
2251
+ });
2252
+ }, [currentConversationId, groupedConversations]);
2253
+
2254
+ useEffect(() => {
2255
+ if (!currentConversationId) {
2256
+ return;
2257
+ }
2258
+ setRecentlyFocusedConversationId(currentConversationId);
2259
+ const timer = window.setTimeout(() => {
2260
+ setRecentlyFocusedConversationId((prev) => (prev === currentConversationId ? null : prev));
2261
+ }, 1200);
2262
+ return () => window.clearTimeout(timer);
2263
+ }, [currentConversationId]);
2264
+
2265
+ const conversationRowRefs = useRef<Map<string, HTMLDivElement>>(new Map());
2266
+ useEffect(() => {
2267
+ if (!currentConversationId) {
2268
+ return;
2269
+ }
2270
+ const row = conversationRowRefs.current.get(currentConversationId);
2271
+ if (!row) {
2272
+ return;
2273
+ }
2274
+ row.scrollIntoView({
2275
+ block: 'nearest',
2276
+ inline: 'nearest',
2277
+ behavior: 'smooth',
2278
+ });
2279
+ }, [currentConversationId, expandedSections, groupedConversations]);
2280
+
2199
2281
  // Build effective customer object with required customerId
2200
2282
  const effectiveCustomer = useMemo(() => {
2201
2283
  return {
@@ -2986,14 +3068,28 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
2986
3068
  <div className="ai-agent-panel__group-label">
2987
3069
  <span>Active ({activeConversationsList.length})</span>
2988
3070
  </div>
2989
- {activeConversationsList.map((activeConv) => (
3071
+ {activeConversationsList.map((activeConv) => {
3072
+ const subtitle =
3073
+ conversationSubtitleResolver?.(activeConv.conversationId, activeConv) || '';
3074
+ return (
2990
3075
  <div
2991
3076
  key={activeConv.stableKey}
2992
3077
  className={`ai-agent-panel__conversation ai-agent-panel__conversation--active-item ${
2993
3078
  currentConversationId === activeConv.conversationId
2994
3079
  ? 'ai-agent-panel__conversation--current'
2995
3080
  : ''
3081
+ } ${
3082
+ recentlyFocusedConversationId === activeConv.conversationId
3083
+ ? 'ai-agent-panel__conversation--just-focused'
3084
+ : ''
2996
3085
  }`}
3086
+ ref={(node) => {
3087
+ if (node) {
3088
+ conversationRowRefs.current.set(activeConv.conversationId, node);
3089
+ } else {
3090
+ conversationRowRefs.current.delete(activeConv.conversationId);
3091
+ }
3092
+ }}
2997
3093
  onClick={() => {
2998
3094
  commitConversationSelection(activeConv.conversationId, true);
2999
3095
  }}
@@ -3003,6 +3099,9 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
3003
3099
  {activeConv.isLoading && <LoadingDotIcon />}
3004
3100
  <span>{activeConv.title}</span>
3005
3101
  </div>
3102
+ {subtitle ? (
3103
+ <div className="ai-agent-panel__conversation-subtitle">{subtitle}</div>
3104
+ ) : null}
3006
3105
  </div>
3007
3106
  <button
3008
3107
  className="ai-agent-panel__conversation-close"
@@ -3012,7 +3111,8 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
3012
3111
  <CloseIcon />
3013
3112
  </button>
3014
3113
  </div>
3015
- ))}
3114
+ );
3115
+ })}
3016
3116
  </div>
3017
3117
  )}
3018
3118
 
@@ -3056,6 +3156,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
3056
3156
  {expandedSections[group.label] && group.conversations.length > 0 && group.conversations.map((conv, convIndex) => {
3057
3157
  // Check if this conversation is already active
3058
3158
  const isActive = activeConversations.has(conv.conversationId);
3159
+ const subtitle = conversationSubtitleResolver?.(conv.conversationId, conv) || '';
3059
3160
  return (
3060
3161
  <div
3061
3162
  key={`${group.label}-${conv.conversationId}-${convIndex}`}
@@ -3063,7 +3164,18 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
3063
3164
  currentConversationId === conv.conversationId
3064
3165
  ? 'ai-agent-panel__conversation--current'
3065
3166
  : ''
3167
+ } ${
3168
+ recentlyFocusedConversationId === conv.conversationId
3169
+ ? 'ai-agent-panel__conversation--just-focused'
3170
+ : ''
3066
3171
  } ${isActive ? 'ai-agent-panel__conversation--in-active' : ''}`}
3172
+ ref={(node) => {
3173
+ if (node) {
3174
+ conversationRowRefs.current.set(conv.conversationId, node);
3175
+ } else {
3176
+ conversationRowRefs.current.delete(conv.conversationId);
3177
+ }
3178
+ }}
3067
3179
  onClick={() => handleConversationSelect(conv)}
3068
3180
  >
3069
3181
  <div className="ai-agent-panel__conversation-content">
@@ -3071,6 +3183,9 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
3071
3183
  {isActive && <span className="ai-agent-panel__active-badge">●</span>}
3072
3184
  {conversationFirstPrompts[conv.conversationId] || conv.title}
3073
3185
  </div>
3186
+ {subtitle ? (
3187
+ <div className="ai-agent-panel__conversation-subtitle">{subtitle}</div>
3188
+ ) : null}
3074
3189
  </div>
3075
3190
  </div>
3076
3191
  );
@@ -3143,6 +3258,8 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
3143
3258
  onToggleSection={handleContextSectionToggle}
3144
3259
  onConversationCreated={handleConversationCreated}
3145
3260
  onBeforeSend={onBeforeSend}
3261
+ compactContext={compactContext}
3262
+ compactionPreserveTurns={compactionPreserveTurns}
3146
3263
  conversationInitialPrompt={activeConv.conversationInitialPrompt}
3147
3264
  cssUrl={cssUrl}
3148
3265
  markdownClass={markdownClass}
@@ -3164,6 +3281,7 @@ const AIAgentPanel = React.forwardRef<AIAgentPanelHandle, AIAgentPanelProps>(({
3164
3281
  traceContextMode={traceContextMode}
3165
3282
  autoApproveTools={autoApproveTools}
3166
3283
  toolStatusLabelFormatter={toolStatusLabelFormatter}
3284
+ composerAgentModeControl={composerAgentModeControl}
3167
3285
  />
3168
3286
  ))}
3169
3287