@sybilion/uilib 1.3.88 → 1.3.90

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.
Files changed (31) hide show
  1. package/dist/esm/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.js +29 -21
  2. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +4 -1
  3. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +17 -10
  4. package/dist/esm/contexts/chat-context.js +180 -205
  5. package/dist/esm/contexts/chatPersistence.js +6 -18
  6. package/dist/esm/contexts/chatSessionStorage.js +245 -0
  7. package/dist/esm/lib/dashboard-spec/jsonDashboardFence.js +80 -0
  8. package/dist/esm/types/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.d.ts +1 -5
  9. package/dist/esm/types/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.test.d.ts +1 -0
  10. package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +6 -1
  11. package/dist/esm/types/src/contexts/chat-context.d.ts +6 -0
  12. package/dist/esm/types/src/contexts/chatPersistence.d.ts +4 -1
  13. package/dist/esm/types/src/contexts/chatSessionStorage.d.ts +32 -0
  14. package/dist/esm/types/src/contexts/chatSessionStorage.test.d.ts +1 -0
  15. package/dist/esm/types/src/lib/dashboard-spec/jsonDashboardFence.d.ts +9 -0
  16. package/dist/esm/types/src/lib/dashboard-spec/stripJsonDashboardFences.d.ts +1 -2
  17. package/dist/esm/types/src/lib/dashboard-spec/stripJsonDashboardFences.test.d.ts +1 -0
  18. package/package.json +1 -1
  19. package/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.test.tsx +25 -0
  20. package/src/components/ui/Chat/ChatMessage/AgentMessageContent.helpers.tsx +35 -26
  21. package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +7 -1
  22. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +21 -8
  23. package/src/contexts/chat-context.tsx +253 -220
  24. package/src/contexts/chatPersistence.test.ts +11 -0
  25. package/src/contexts/chatPersistence.ts +22 -6
  26. package/src/contexts/chatSessionStorage.test.ts +125 -0
  27. package/src/contexts/chatSessionStorage.ts +321 -0
  28. package/src/lib/dashboard-spec/jsonDashboardFence.ts +98 -0
  29. package/src/lib/dashboard-spec/stripJsonDashboardFences.test.ts +84 -0
  30. package/src/lib/dashboard-spec/stripJsonDashboardFences.ts +5 -6
  31. package/dist/esm/lib/dashboard-spec/stripJsonDashboardFences.js +0 -7
@@ -4,27 +4,6 @@ import logger from '../../../../lib/logger.js';
4
4
  import S from './ChatMessage.styl.js';
5
5
  import S$1 from '../../InteractiveContent/InteractiveContent.styl.js';
6
6
 
7
- const injectHeaders = (content) => {
8
- // Match #, ##, ###, or #### headers at start of line
9
- const regex = /(^|\n)(#{1,4})\s+(.+?)(?=\n|$)/m;
10
- const matches = content.match(regex);
11
- if (!matches)
12
- return null;
13
- const level = matches[2].length;
14
- const headerText = matches[3].replace(/^\*+|\*+$/g, '');
15
- const Tag = level === 1 ? 'h1' : level === 2 ? 'h2' : level === 3 ? 'h3' : 'h4';
16
- // Calculate the actual match position and length
17
- // matches[0] includes the leading \n if present, but we want to replace from the # position
18
- const hasLeadingNewline = matches[1] === '\n';
19
- const startIndex = matches.index + (hasLeadingNewline ? 1 : 0);
20
- // Length is: # markers + space + header text (excluding leading newline)
21
- const length = matches[2].length + 1 + matches[3].length;
22
- return {
23
- elem: jsx(Tag, { children: headerText }),
24
- index: startIndex,
25
- length: length,
26
- };
27
- };
28
7
  /** Match sits inside list/table HTML (those blocks use dangerouslySetInnerHTML elsewhere). */
29
8
  const isInsideHtmlListOrTable = (content, matchStartIndex) => {
30
9
  const before = content.substring(0, matchStartIndex);
@@ -402,6 +381,35 @@ const injectAnchor = (content) => {
402
381
  length: matches[0].length,
403
382
  };
404
383
  };
384
+ const headerTextInjectors = [
385
+ injectAnchor,
386
+ injectMarkdownLink,
387
+ injectHTMLTags,
388
+ injectBold,
389
+ injectItalic,
390
+ injectAutolinkUrl,
391
+ ];
392
+ const injectHeaders = (content) => {
393
+ // Match #, ##, ###, or #### headers at start of line
394
+ const regex = /(^|\n)(#{1,4})\s+(.+?)(?=\n|$)/m;
395
+ const matches = content.match(regex);
396
+ if (!matches)
397
+ return null;
398
+ const level = matches[2].length;
399
+ const headerText = matches[3].replace(/^\*+|\*+$/g, '');
400
+ const Tag = level === 1 ? 'h1' : level === 2 ? 'h2' : level === 3 ? 'h3' : 'h4';
401
+ // Calculate the actual match position and length
402
+ // matches[0] includes the leading \n if present, but we want to replace from the # position
403
+ const hasLeadingNewline = matches[1] === '\n';
404
+ const startIndex = matches.index + (hasLeadingNewline ? 1 : 0);
405
+ // Length is: # markers + space + header text (excluding leading newline)
406
+ const length = matches[2].length + 1 + matches[3].length;
407
+ return {
408
+ elem: jsx(Tag, { children: runFormattingPipeline(headerText, headerTextInjectors) }),
409
+ index: startIndex,
410
+ length: length,
411
+ };
412
+ };
405
413
  const applyFormatting = (text) => runFormattingPipeline(text, [
406
414
  injectHeaders,
407
415
  injectAnchor,
@@ -1,9 +1,11 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import cn from 'classnames';
3
+ import { useMemo } from 'react';
3
4
  import { InteractiveContent } from '../../InteractiveContent/InteractiveContent.js';
4
5
  import 'lucide-react';
5
6
  import '../../InteractiveContent/InteractiveContent.styl.js';
6
7
  import { TextShimmer } from '../../TextShimmer/TextShimmer.js';
8
+ import { stripJsonDashboardFences } from '../../../../lib/dashboard-spec/jsonDashboardFence.js';
7
9
  import { MessageRole } from '../Chat.types.js';
8
10
  import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments.js';
9
11
  import { AgentMessageContent } from './AgentMessageContent.js';
@@ -16,7 +18,8 @@ function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickR
16
18
  });
17
19
  const isAssistant = role === MessageRole.ASSISTANT;
18
20
  const isSystem = role === MessageRole.SYSTEM;
19
- return (jsx("div", { className: cn(S.root, S[`role-${role}`], className), children: isSystem ? (jsx("div", { className: cn(S.text, textClassName), children: inProgress ? (jsx(TextShimmer, { as: "span", children: text })) : renderSystemMessage && message ? (renderSystemMessage(message)) : (text) })) : isAssistant ? (jsx(AgentMessageContent, { text: text, textClassName: textClassName, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, quickReplyHidden: quickReplyHidden, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: cn(S.text, textClassName && S.textCustom, textClassName), children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
21
+ const assistantDisplayText = useMemo(() => (isAssistant ? stripJsonDashboardFences(text) : text), [isAssistant, text]);
22
+ return (jsx("div", { className: cn(S.root, S[`role-${role}`], className), children: isSystem ? (jsx("div", { className: cn(S.text, textClassName), children: inProgress ? (jsx(TextShimmer, { as: "span", children: text })) : renderSystemMessage && message ? (renderSystemMessage(message)) : (text) })) : isAssistant ? (jsx(AgentMessageContent, { text: assistantDisplayText, textClassName: textClassName, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, quickReplyHidden: quickReplyHidden, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: cn(S.text, textClassName && S.textCustom, textClassName), children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
20
23
  }
21
24
 
22
25
  export { ChatMessage };
@@ -22,7 +22,7 @@ const CHAT_NEW_SHORTCUT_KEY = 'o';
22
22
  const CHAT_QUERY_PARAM = 'chat';
23
23
  const CHAT_OPEN_VALUE = 'open';
24
24
  const PROMPT_QUERY_PARAM = 'prompt';
25
- function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, renderSystemMessage, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, copyHistoryOnNewChat = false, transformSendPayload, }) {
25
+ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, renderSystemMessage, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, copyHistoryOnNewChat = false, transformSendPayload, submitPresetsViaApi = false, }) {
26
26
  const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
27
27
  const isMobile = useIsMobile();
28
28
  const { chatPanelContainer, isOpen: sidebarNavOpen, setOpen: setSidebarNavOpen, sidebarWidthPx, chatWidthPx, setChatWidthPx, getShellWidth, chatPanelOpen: shellChatPanelOpen, setChatPanelOpen, } = useSidebar();
@@ -350,7 +350,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
350
350
  setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
351
351
  try {
352
352
  const { response: assistantResponse, sessionId } = await sendMessage(payload);
353
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse, sessionId);
353
+ await onMessage?.(displayTextFromSendPayload(payload), assistantResponse, sessionId);
354
354
  }
355
355
  finally {
356
356
  setOutboundLoadingLabel(undefined);
@@ -488,7 +488,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
488
488
  setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
489
489
  try {
490
490
  const { response: assistantResponse, sessionId } = await sendMessage(payload);
491
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse, sessionId);
491
+ await onMessage?.(displayTextFromSendPayload(payload), assistantResponse, sessionId);
492
492
  }
493
493
  finally {
494
494
  setOutboundLoadingLabel(undefined);
@@ -523,14 +523,19 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
523
523
  const scriptGraph = isPresetScriptGraph(script);
524
524
  const hasLinearScript = Array.isArray(script) && script.length > 0;
525
525
  const hasReplies = preset.replies && Object.keys(preset.replies).length > 0;
526
- const isLocalDemo = hasLinearScript ||
527
- scriptGraph ||
528
- Boolean(preset.answer?.trim()) ||
529
- Boolean(hasReplies);
526
+ const isLocalDemo = !submitPresetsViaApi &&
527
+ (hasLinearScript ||
528
+ scriptGraph ||
529
+ Boolean(preset.answer?.trim()) ||
530
+ Boolean(hasReplies));
530
531
  if (!isLocalDemo) {
531
- if (!currentChatId)
532
- return;
533
- endLocalDemoFlow(currentChatId);
532
+ let chatId = currentChatId;
533
+ if (!chatId) {
534
+ chatId = startEmptyNewChat() ?? undefined;
535
+ if (!chatId)
536
+ return;
537
+ }
538
+ endLocalDemoFlow(chatId);
534
539
  await handlePromptSubmit(options?.message ?? preset.text);
535
540
  return;
536
541
  }
@@ -613,10 +618,12 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
613
618
  }
614
619
  }, [
615
620
  currentChatId,
621
+ startEmptyNewChat,
616
622
  endLocalDemoFlow,
617
623
  handlePromptSubmit,
618
624
  addMessage,
619
625
  presetsWithFreeform,
626
+ submitPresetsViaApi,
620
627
  ]);
621
628
  const resolvedEmptyState = useMemo(() => {
622
629
  if (!emptyState)