@sybilion/uilib 1.3.38 → 1.3.39

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.
@@ -4,12 +4,5 @@ var MessageRole;
4
4
  MessageRole["ASSISTANT"] = "assistant";
5
5
  MessageRole["SYSTEM"] = "system";
6
6
  })(MessageRole || (MessageRole = {}));
7
- /** System placeholder while dashboard generation runs (must match ChatSheet `addMessage` text). */
8
- const GENERATING_DASHBOARD_SYSTEM_TEXT = 'Generating dashboard…';
9
- /** Slash command id that triggers dashboard generation when `onGenerateDashboard` is set. */
10
- const GENERATE_DASHBOARD_SLASH_COMMAND_ID = 'generate-dashboard';
11
- function isGenerateDashboardSlashMessage(message) {
12
- return message.trim() === `/${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`;
13
- }
14
7
 
15
- export { GENERATE_DASHBOARD_SLASH_COMMAND_ID, GENERATING_DASHBOARD_SYSTEM_TEXT, MessageRole, isGenerateDashboardSlashMessage };
8
+ export { MessageRole };
@@ -3,8 +3,7 @@ import cn from 'classnames';
3
3
  import { InteractiveContent } from '../../InteractiveContent/InteractiveContent.js';
4
4
  import 'lucide-react';
5
5
  import '../../InteractiveContent/InteractiveContent.styl.js';
6
- import { TextShimmer } from '../../TextShimmer/TextShimmer.js';
7
- import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT } from '../Chat.types.js';
6
+ import { MessageRole } from '../Chat.types.js';
8
7
  import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments.js';
9
8
  import { AgentMessageContent } from './AgentMessageContent.js';
10
9
  import S from './ChatMessage.styl.js';
@@ -16,7 +15,7 @@ function ChatMessage({ role, text, userTextFileAttachments, onQuickReply, suppre
16
15
  });
17
16
  const isAssistant = role === MessageRole.ASSISTANT;
18
17
  const isSystem = role === MessageRole.SYSTEM;
19
- return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: text === GENERATING_DASHBOARD_SYSTEM_TEXT ? (jsx(TextShimmer, { as: "span", children: text })) : (text) })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
18
+ return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: text })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
20
19
  }
21
20
 
22
21
  export { ChatMessage };
@@ -4,20 +4,20 @@ import { Button } from '../../Button/Button.js';
4
4
  import { ChatChrome } from '../ChatChrome/ChatChrome.js';
5
5
  import { useChatPanelChromeModel } from './useChatPanelChromeModel.js';
6
6
 
7
- function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, inline = false, }) {
7
+ function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, inline = false, }) {
8
8
  const model = useChatPanelChromeModel({
9
9
  embedAsPage: inline,
10
10
  presets,
11
11
  scopeId,
12
12
  onMessage,
13
13
  onScriptComplete,
14
- onGenerateDashboard,
15
14
  renderMessageChart,
16
15
  emptyState,
17
16
  allowedAttachments,
18
17
  allowPdfAttachments,
19
18
  onAttachmentsDropped,
20
19
  slashCommandItems,
20
+ onSlashItemCommand,
21
21
  });
22
22
  if (actionsRef) {
23
23
  actionsRef.current = {
@@ -1,9 +1,9 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
3
- import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT, GENERATE_DASHBOARD_SLASH_COMMAND_ID, isGenerateDashboardSlashMessage } from '../Chat.types.js';
3
+ import { MessageRole } from '../Chat.types.js';
4
4
  import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, isPresetScriptGraph, branchesFromPresetScriptGraph, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant } from '../ChatMessage/presetScript.js';
5
5
  import { buildChatSendMessagePayload, displayTextFromSendPayload } from '../buildChatSendMessagePayload.js';
6
- import { usedPresetIdsFromMessages, formatChatTranscript } from '../chat-preset-utils.js';
6
+ import { usedPresetIdsFromMessages } from '../chat-preset-utils.js';
7
7
  import { useChatsForScopeId, useChat, useChatOutboundPending, useSyncChatPanelBusy, isChatEmpty } from '../../../../contexts/chat-context.js';
8
8
  import useEvent from '../../../../hooks/useEvent.js';
9
9
  import { useIsMobile } from '../../../../hooks/useIsMobile.js';
@@ -22,7 +22,7 @@ const CHAT_NAV_COLLAPSE_BREAKPOINT_PX = 1400;
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, onGenerateDashboard, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, }) {
25
+ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, }) {
26
26
  const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
27
27
  const isMobile = useIsMobile();
28
28
  const { chatPanelContainer, isOpen: sidebarNavOpen, setOpen: setSidebarNavOpen, chatWidthPx, setChatWidthPx, getShellWidth, setChatPanelOpen, } = useSidebar();
@@ -60,7 +60,6 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
60
60
  const [intakeByChatId, setIntakeByChatId] = useState({});
61
61
  /** Preset intake finished for this session (e.g. `onScriptComplete` callback). */
62
62
  const [scriptCompleteByChatId, setScriptCompleteByChatId] = useState({});
63
- const [generatingDashboard, setGeneratingDashboard] = useState(false);
64
63
  const scriptAdvanceLockRef = useRef(false);
65
64
  const quickReplyLockRef = useRef(false);
66
65
  const scrollRef = useRef(null);
@@ -712,60 +711,14 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
712
711
  lastMsg?.role === MessageRole.ASSISTANT &&
713
712
  !linearScriptActive &&
714
713
  (!graphActive || (!lastHasQuickMarkers && unusedBranchKeys.length === 0));
715
- const handleGenerateDashboard = useCallback(async () => {
716
- if (!currentChatId || !onGenerateDashboard)
717
- return;
718
- const transcript = formatChatTranscript((chat?.messages ?? []).filter(m => m.role !== MessageRole.SYSTEM));
719
- setGeneratingDashboard(true);
720
- const systemPlaceholderId = addMessage(currentChatId, MessageRole.SYSTEM, GENERATING_DASHBOARD_SYSTEM_TEXT);
721
- try {
722
- await Promise.resolve(onGenerateDashboard(transcript));
723
- setScriptCompleteByChatId(prev => {
724
- const next = { ...prev };
725
- delete next[currentChatId];
726
- return next;
727
- });
728
- }
729
- finally {
730
- if (systemPlaceholderId) {
731
- removeMessageById(currentChatId, systemPlaceholderId);
732
- }
733
- setGeneratingDashboard(false);
734
- }
735
- }, [
736
- currentChatId,
737
- onGenerateDashboard,
738
- chat?.messages,
739
- addMessage,
740
- removeMessageById,
741
- ]);
742
- const onSlashItemCommand = useCallback(({ item }) => {
743
- if (item.id !== GENERATE_DASHBOARD_SLASH_COMMAND_ID ||
744
- !onGenerateDashboard) {
745
- return false;
746
- }
747
- if (generatingDashboard) {
748
- return true;
749
- }
750
- queueMicrotask(() => {
751
- void handleGenerateDashboard();
752
- });
753
- return true;
754
- }, [onGenerateDashboard, generatingDashboard, handleGenerateDashboard]);
755
714
  const onPromptSubmitWithSlashCommands = useCallback(async (message, attachments) => {
756
- if (isGenerateDashboardSlashMessage(message) &&
757
- onGenerateDashboard &&
758
- !generatingDashboard) {
759
- void handleGenerateDashboard();
715
+ const trimmed = message.trim();
716
+ const slashItem = slashCommandItems?.find(item => trimmed === `/${item.id}`);
717
+ if (slashItem && onSlashItemCommand?.({ item: slashItem }) === true) {
760
718
  return;
761
719
  }
762
720
  return handlePromptSubmit(message, attachments);
763
- }, [
764
- handlePromptSubmit,
765
- onGenerateDashboard,
766
- generatingDashboard,
767
- handleGenerateDashboard,
768
- ]);
721
+ }, [handlePromptSubmit, slashCommandItems, onSlashItemCommand]);
769
722
  const onDragChatWidth = useCallback((rawPx) => setChatWidthPx(rawPx, { persist: false }), [setChatWidthPx]);
770
723
  const onDragChatComplete = useCallback((finalRawPx) => setChatWidthPx(finalRawPx, { persist: true }), [setChatWidthPx]);
771
724
  const chromeProps = {
package/dist/esm/index.js CHANGED
@@ -22,7 +22,7 @@ export { ChartLegend, ChartTooltip } from './components/ui/Chart/Chart.js';
22
22
  export { THEMES } from './components/ui/Chart/Chart.types.js';
23
23
  export { ChartAreaInteractive, chartConfig } from './components/ui/ChartAreaInteractive/ChartAreaInteractive.js';
24
24
  export { Chat } from './components/ui/Chat/Chat.js';
25
- export { usedPresetIdsFromMessages } from './components/ui/Chat/chat-preset-utils.js';
25
+ export { formatChatTranscript, usedPresetIdsFromMessages } from './components/ui/Chat/chat-preset-utils.js';
26
26
  export { ChatChrome } from './components/ui/Chat/ChatChrome/ChatChrome.js';
27
27
  export { TEXT_ATTACHMENT_ACCEPT_PARTS, filterToTextAttachments } from './components/ui/Chat/chatAttachmentAccept.js';
28
28
  export { buildChatSendMessagePayload, displayTextFromSendPayload, normalizeUserTextFileAttachments } from './components/ui/Chat/buildChatSendMessagePayload.js';
@@ -32,7 +32,7 @@ export { useChatPanelChromeModel } from './components/ui/Chat/ChatSheet/useChatP
32
32
  export { ChatMessage } from './components/ui/Chat/ChatMessage/ChatMessage.js';
33
33
  export { ChatPrompt } from './components/ui/Chat/ChatPrompt/ChatPrompt.js';
34
34
  export { ChatPresets } from './components/ui/Chat/ChatPresets/ChatPresets.js';
35
- export { GENERATE_DASHBOARD_SLASH_COMMAND_ID, MessageRole, isGenerateDashboardSlashMessage } from './components/ui/Chat/Chat.types.js';
35
+ export { MessageRole } from './components/ui/Chat/Chat.types.js';
36
36
  export { CsvIcon } from './components/icons/CsvIcon/CsvIcon.js';
37
37
  export { Checkbox } from './components/ui/Checkbox/Checkbox.js';
38
38
  export { Confirm } from './components/ui/Confirm/Confirm.js';
@@ -6,11 +6,6 @@ export declare enum MessageRole {
6
6
  ASSISTANT = "assistant",
7
7
  SYSTEM = "system"
8
8
  }
9
- /** System placeholder while dashboard generation runs (must match ChatSheet `addMessage` text). */
10
- export declare const GENERATING_DASHBOARD_SYSTEM_TEXT = "Generating dashboard\u2026";
11
- /** Slash command id that triggers dashboard generation when `onGenerateDashboard` is set. */
12
- export declare const GENERATE_DASHBOARD_SLASH_COMMAND_ID = "generate-dashboard";
13
- export declare function isGenerateDashboardSlashMessage(message: string): boolean;
14
9
  /** USER-only: text file attached to a message; shown as downloadable file row(s). */
15
10
  export type UserTextFileAttachment = {
16
11
  displayName: string;
@@ -19,4 +19,4 @@ export interface ChatSheetProps extends Omit<UseChatPanelChromeModelInput, 'embe
19
19
  */
20
20
  inline?: boolean;
21
21
  }
22
- export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, inline, }: ChatSheetProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, inline, }: ChatSheetProps): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { ChatPreset, type ScriptCompletePayload } from '#uilib/components/ui/Chat/Chat.types';
2
- import type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
2
+ import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
3
3
  import type { ChatChromeProps } from '../ChatChrome';
4
4
  import type { ChatAttachmentDropItem } from '../ChatChrome/ChatChrome.types';
5
5
  import type { ChatEmptyStateConfig } from '../ChatEmptyState/ChatEmptyState.types';
@@ -12,8 +12,6 @@ export type UseChatPanelChromeModelInput = {
12
12
  onMessage?: (message: string) => void;
13
13
  /** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
14
14
  onScriptComplete?: (payload: ScriptCompletePayload) => void;
15
- /** Generate dashboard from chat transcript (e.g. via `/generate-dashboard` slash command). */
16
- onGenerateDashboard?: (transcript: string) => void | Promise<void>;
17
15
  /** Renders `[CHART]` tokens in assistant messages. */
18
16
  renderMessageChart?: () => React.ReactNode;
19
17
  /** Forwarded to `ChatChrome` when the thread is empty. */
@@ -25,6 +23,8 @@ export type UseChatPanelChromeModelInput = {
25
23
  onAttachmentsDropped?: (items: ChatAttachmentDropItem[]) => void | Promise<void>;
26
24
  /** Slash menu (`/`) in the composer; omit or pass empty to disable. */
27
25
  slashCommandItems?: SlashCommandItem[];
26
+ /** Custom slash command handler (palette pick or Enter on `/id`). */
27
+ onSlashItemCommand?: SlashOnItemCommand;
28
28
  };
29
29
  export type UseChatPanelChromeModelResult = {
30
30
  chromeProps: ChatChromeProps;
@@ -34,4 +34,4 @@ export type UseChatPanelChromeModelResult = {
34
34
  newChat: () => void;
35
35
  chatPanelContainer: HTMLElement | null;
36
36
  };
37
- export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, onGenerateDashboard, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
37
+ export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
@@ -1,5 +1,5 @@
1
1
  export { Chat } from './Chat';
2
- export { usedPresetIdsFromMessages } from './chat-preset-utils';
2
+ export { formatChatTranscript, usedPresetIdsFromMessages, } from './chat-preset-utils';
3
3
  export { ChatChrome } from './ChatChrome';
4
4
  export type { ChatChromeProps, ChatChromeResizeHandleConfig, } from './ChatChrome';
5
5
  export { TEXT_ATTACHMENT_ACCEPT_PARTS, filterToTextAttachments, } from './chatAttachmentAccept';
@@ -14,6 +14,6 @@ export { ChatPrompt } from './ChatPrompt';
14
14
  export { ChatPresets } from './ChatPresets';
15
15
  export type { ChatEmptyStateConfig, ChatEmptyStateContext, ChatEmptyStateProps, } from './ChatEmptyState/ChatEmptyState.types';
16
16
  export type { Chat as ChatType, ChatAttachmentDropItem, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserTextFileAttachment, } from './Chat.types';
17
- export { GENERATE_DASHBOARD_SLASH_COMMAND_ID, isGenerateDashboardSlashMessage, MessageRole, } from './Chat.types';
18
- export type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
17
+ export { MessageRole } from './Chat.types';
18
+ export type { SlashCommandItem, SlashItemCommandContext, SlashOnItemCommand, } from '#uilib/tiptap/slash-mention/types';
19
19
  export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
@@ -5,9 +5,10 @@ export type SlashCommandItem = {
5
5
  description?: string;
6
6
  };
7
7
  export type SlashItemCommandContext = {
8
- editor: Editor;
9
- range: Range;
10
8
  item: SlashCommandItem;
9
+ /** Present when picked from the slash palette; omitted on Enter submit of `/id`. */
10
+ editor?: Editor;
11
+ range?: Range;
11
12
  };
12
13
  /**
13
14
  * If provided, run when a slash item is picked. Return true to skip mention insert
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.38",
3
+ "version": "1.3.39",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -13,16 +13,6 @@ export enum MessageRole {
13
13
  SYSTEM = 'system',
14
14
  }
15
15
 
16
- /** System placeholder while dashboard generation runs (must match ChatSheet `addMessage` text). */
17
- export const GENERATING_DASHBOARD_SYSTEM_TEXT = 'Generating dashboard…';
18
-
19
- /** Slash command id that triggers dashboard generation when `onGenerateDashboard` is set. */
20
- export const GENERATE_DASHBOARD_SLASH_COMMAND_ID = 'generate-dashboard';
21
-
22
- export function isGenerateDashboardSlashMessage(message: string): boolean {
23
- return message.trim() === `/${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`;
24
- }
25
-
26
16
  /** USER-only: text file attached to a message; shown as downloadable file row(s). */
27
17
  export type UserTextFileAttachment = {
28
18
  displayName: string;
@@ -2,12 +2,7 @@ import cn from 'classnames';
2
2
 
3
3
  import { InteractiveContent } from '#uilib/components/ui/InteractiveContent';
4
4
 
5
- import { TextShimmer } from '../../TextShimmer';
6
- import {
7
- type ChatMessageProps,
8
- GENERATING_DASHBOARD_SYSTEM_TEXT,
9
- MessageRole,
10
- } from '../Chat.types';
5
+ import { type ChatMessageProps, MessageRole } from '../Chat.types';
11
6
  import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments';
12
7
  import { AgentMessageContent } from './AgentMessageContent';
13
8
  import S from './ChatMessage.styl';
@@ -34,13 +29,7 @@ export function ChatMessage({
34
29
  return (
35
30
  <div className={cn(S.root, S[`role-${role}`])}>
36
31
  {isSystem ? (
37
- <div className={S.text}>
38
- {text === GENERATING_DASHBOARD_SYSTEM_TEXT ? (
39
- <TextShimmer as="span">{text}</TextShimmer>
40
- ) : (
41
- text
42
- )}
43
- </div>
32
+ <div className={S.text}>{text}</div>
44
33
  ) : isAssistant ? (
45
34
  <AgentMessageContent
46
35
  text={text}
@@ -41,13 +41,13 @@ export function ChatSheet({
41
41
  scopeId,
42
42
  onMessage,
43
43
  onScriptComplete,
44
- onGenerateDashboard,
45
44
  renderMessageChart,
46
45
  emptyState,
47
46
  allowedAttachments,
48
47
  allowPdfAttachments,
49
48
  onAttachmentsDropped,
50
49
  slashCommandItems,
50
+ onSlashItemCommand,
51
51
  inline = false,
52
52
  }: ChatSheetProps) {
53
53
  const model = useChatPanelChromeModel({
@@ -56,13 +56,13 @@ export function ChatSheet({
56
56
  scopeId,
57
57
  onMessage,
58
58
  onScriptComplete,
59
- onGenerateDashboard,
60
59
  renderMessageChart,
61
60
  emptyState,
62
61
  allowedAttachments,
63
62
  allowPdfAttachments,
64
63
  onAttachmentsDropped,
65
64
  slashCommandItems,
65
+ onSlashItemCommand,
66
66
  });
67
67
 
68
68
  if (actionsRef) {
@@ -2,11 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
3
  import {
4
4
  ChatPreset,
5
- GENERATE_DASHBOARD_SLASH_COMMAND_ID,
6
- GENERATING_DASHBOARD_SYSTEM_TEXT,
7
5
  MessageRole,
8
6
  type ScriptCompletePayload,
9
- isGenerateDashboardSlashMessage,
10
7
  } from '#uilib/components/ui/Chat/Chat.types';
11
8
  import {
12
9
  branchKeysUsedByUserMessages,
@@ -25,10 +22,7 @@ import {
25
22
  buildChatSendMessagePayload,
26
23
  displayTextFromSendPayload,
27
24
  } from '#uilib/components/ui/Chat/buildChatSendMessagePayload';
28
- import {
29
- formatChatTranscript,
30
- usedPresetIdsFromMessages,
31
- } from '#uilib/components/ui/Chat/chat-preset-utils';
25
+ import { usedPresetIdsFromMessages } from '#uilib/components/ui/Chat/chat-preset-utils';
32
26
  import {
33
27
  isChatEmpty,
34
28
  useChat,
@@ -42,7 +36,7 @@ import { useQueryParams } from '#uilib/hooks/useQueryParams';
42
36
  import logger from '#uilib/lib/logger';
43
37
  import type {
44
38
  SlashCommandItem,
45
- SlashItemCommandContext,
39
+ SlashOnItemCommand,
46
40
  } from '#uilib/tiptap/slash-mention/types';
47
41
  import { mergePresetFreeformDefaults } from '#uilib/utils/chatPresetMerge';
48
42
  import { ScrollRef } from '@homecode/ui';
@@ -63,8 +57,6 @@ export type UseChatPanelChromeModelInput = {
63
57
  onMessage?: (message: string) => void;
64
58
  /** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
65
59
  onScriptComplete?: (payload: ScriptCompletePayload) => void;
66
- /** Generate dashboard from chat transcript (e.g. via `/generate-dashboard` slash command). */
67
- onGenerateDashboard?: (transcript: string) => void | Promise<void>;
68
60
  /** Renders `[CHART]` tokens in assistant messages. */
69
61
  renderMessageChart?: () => React.ReactNode;
70
62
  /** Forwarded to `ChatChrome` when the thread is empty. */
@@ -78,6 +70,8 @@ export type UseChatPanelChromeModelInput = {
78
70
  ) => void | Promise<void>;
79
71
  /** Slash menu (`/`) in the composer; omit or pass empty to disable. */
80
72
  slashCommandItems?: SlashCommandItem[];
73
+ /** Custom slash command handler (palette pick or Enter on `/id`). */
74
+ onSlashItemCommand?: SlashOnItemCommand;
81
75
  };
82
76
 
83
77
  export type UseChatPanelChromeModelResult = {
@@ -120,13 +114,13 @@ export function useChatPanelChromeModel({
120
114
  scopeId,
121
115
  onMessage,
122
116
  onScriptComplete,
123
- onGenerateDashboard,
124
117
  renderMessageChart,
125
118
  emptyState,
126
119
  allowedAttachments,
127
120
  allowPdfAttachments,
128
121
  onAttachmentsDropped,
129
122
  slashCommandItems,
123
+ onSlashItemCommand,
130
124
  }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult {
131
125
  const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
132
126
  const isMobile = useIsMobile();
@@ -202,7 +196,6 @@ export function useChatPanelChromeModel({
202
196
  const [scriptCompleteByChatId, setScriptCompleteByChatId] = useState<
203
197
  Record<string, boolean>
204
198
  >({});
205
- const [generatingDashboard, setGeneratingDashboard] = useState(false);
206
199
  const scriptAdvanceLockRef = useRef(false);
207
200
  const quickReplyLockRef = useRef(false);
208
201
  const scrollRef = useRef<ScrollRef>(null);
@@ -961,75 +954,18 @@ export function useChatPanelChromeModel({
961
954
  !linearScriptActive &&
962
955
  (!graphActive || (!lastHasQuickMarkers && unusedBranchKeys.length === 0));
963
956
 
964
- const handleGenerateDashboard = useCallback(async () => {
965
- if (!currentChatId || !onGenerateDashboard) return;
966
- const transcript = formatChatTranscript(
967
- (chat?.messages ?? []).filter(m => m.role !== MessageRole.SYSTEM),
968
- );
969
- setGeneratingDashboard(true);
970
- const systemPlaceholderId = addMessage(
971
- currentChatId,
972
- MessageRole.SYSTEM,
973
- GENERATING_DASHBOARD_SYSTEM_TEXT,
974
- );
975
- try {
976
- await Promise.resolve(onGenerateDashboard(transcript));
977
- setScriptCompleteByChatId(prev => {
978
- const next = { ...prev };
979
- delete next[currentChatId];
980
- return next;
981
- });
982
- } finally {
983
- if (systemPlaceholderId) {
984
- removeMessageById(currentChatId, systemPlaceholderId);
985
- }
986
- setGeneratingDashboard(false);
987
- }
988
- }, [
989
- currentChatId,
990
- onGenerateDashboard,
991
- chat?.messages,
992
- addMessage,
993
- removeMessageById,
994
- ]);
995
-
996
- const onSlashItemCommand = useCallback(
997
- ({ item }: SlashItemCommandContext) => {
998
- if (
999
- item.id !== GENERATE_DASHBOARD_SLASH_COMMAND_ID ||
1000
- !onGenerateDashboard
1001
- ) {
1002
- return false;
1003
- }
1004
- if (generatingDashboard) {
1005
- return true;
1006
- }
1007
- queueMicrotask(() => {
1008
- void handleGenerateDashboard();
1009
- });
1010
- return true;
1011
- },
1012
- [onGenerateDashboard, generatingDashboard, handleGenerateDashboard],
1013
- );
1014
-
1015
957
  const onPromptSubmitWithSlashCommands = useCallback(
1016
958
  async (message: string, attachments?: ChatAttachmentDropItem[]) => {
1017
- if (
1018
- isGenerateDashboardSlashMessage(message) &&
1019
- onGenerateDashboard &&
1020
- !generatingDashboard
1021
- ) {
1022
- void handleGenerateDashboard();
959
+ const trimmed = message.trim();
960
+ const slashItem = slashCommandItems?.find(
961
+ item => trimmed === `/${item.id}`,
962
+ );
963
+ if (slashItem && onSlashItemCommand?.({ item: slashItem }) === true) {
1023
964
  return;
1024
965
  }
1025
966
  return handlePromptSubmit(message, attachments);
1026
967
  },
1027
- [
1028
- handlePromptSubmit,
1029
- onGenerateDashboard,
1030
- generatingDashboard,
1031
- handleGenerateDashboard,
1032
- ],
968
+ [handlePromptSubmit, slashCommandItems, onSlashItemCommand],
1033
969
  );
1034
970
 
1035
971
  const onDragChatWidth = useCallback(
@@ -1,5 +1,8 @@
1
1
  export { Chat } from './Chat';
2
- export { usedPresetIdsFromMessages } from './chat-preset-utils';
2
+ export {
3
+ formatChatTranscript,
4
+ usedPresetIdsFromMessages,
5
+ } from './chat-preset-utils';
3
6
  export { ChatChrome } from './ChatChrome';
4
7
  export type {
5
8
  ChatChromeProps,
@@ -39,10 +42,10 @@ export type {
39
42
  Message,
40
43
  UserTextFileAttachment,
41
44
  } from './Chat.types';
42
- export {
43
- GENERATE_DASHBOARD_SLASH_COMMAND_ID,
44
- isGenerateDashboardSlashMessage,
45
- MessageRole,
46
- } from './Chat.types';
47
- export type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
45
+ export { MessageRole } from './Chat.types';
46
+ export type {
47
+ SlashCommandItem,
48
+ SlashItemCommandContext,
49
+ SlashOnItemCommand,
50
+ } from '#uilib/tiptap/slash-mention/types';
48
51
  export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
@@ -157,9 +157,6 @@ export default function ChatSlashCommandsPage() {
157
157
  title: 'Try a slash command',
158
158
  description:
159
159
  'Pick sample-command from the palette or send /sample-command — onSlashItemCommand clears the composer and runs the demo action.',
160
- additionalContent: (
161
- <p>Optional empty-state slot via additionalContent.</p>
162
- ),
163
160
  }}
164
161
  />
165
162
  </PageContentSection>
@@ -7,9 +7,10 @@ export type SlashCommandItem = {
7
7
  };
8
8
 
9
9
  export type SlashItemCommandContext = {
10
- editor: Editor;
11
- range: Range;
12
10
  item: SlashCommandItem;
11
+ /** Present when picked from the slash palette; omitted on Enter submit of `/id`. */
12
+ editor?: Editor;
13
+ range?: Range;
13
14
  };
14
15
 
15
16
  /**
@@ -1,32 +0,0 @@
1
- import {
2
- GENERATE_DASHBOARD_SLASH_COMMAND_ID,
3
- isGenerateDashboardSlashMessage,
4
- } from './Chat.types';
5
-
6
- describe('isGenerateDashboardSlashMessage', () => {
7
- it('matches exact slash command', () => {
8
- expect(
9
- isGenerateDashboardSlashMessage(
10
- `/${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`,
11
- ),
12
- ).toBe(true);
13
- });
14
-
15
- it('trims surrounding whitespace', () => {
16
- expect(
17
- isGenerateDashboardSlashMessage(
18
- ` /${GENERATE_DASHBOARD_SLASH_COMMAND_ID} `,
19
- ),
20
- ).toBe(true);
21
- });
22
-
23
- it('rejects plain text and partial commands', () => {
24
- expect(isGenerateDashboardSlashMessage('generate-dashboard')).toBe(false);
25
- expect(isGenerateDashboardSlashMessage('/generate')).toBe(false);
26
- expect(
27
- isGenerateDashboardSlashMessage(
28
- `/ ${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`,
29
- ),
30
- ).toBe(false);
31
- });
32
- });