@sybilion/uilib 1.3.36 → 1.3.38

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 (39) hide show
  1. package/dist/esm/components/ui/Chat/Chat.types.js +6 -1
  2. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +7 -10
  3. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +2 -1
  4. package/dist/esm/components/ui/Chat/ChatPrompt/useChatPromptEditor.js +6 -2
  5. package/dist/esm/components/ui/Chat/ChatSheet/ChatSheet.js +2 -1
  6. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +33 -15
  7. package/dist/esm/index.js +1 -1
  8. package/dist/esm/tiptap/slash-mention/createSlashMentionExtension.js +48 -14
  9. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +6 -1
  10. package/dist/esm/types/src/components/ui/Chat/Chat.types.test.d.ts +1 -0
  11. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
  12. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +3 -6
  13. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
  14. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.d.ts +3 -2
  15. package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSheet.d.ts +1 -1
  16. package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +5 -2
  17. package/dist/esm/types/src/components/ui/Chat/index.d.ts +1 -1
  18. package/dist/esm/types/src/docs/docsHeaderActions.d.ts +2 -1
  19. package/dist/esm/types/src/tiptap/slash-mention/createSlashMentionExtension.d.ts +3 -3
  20. package/dist/esm/types/src/tiptap/slash-mention/index.d.ts +1 -1
  21. package/dist/esm/types/src/tiptap/slash-mention/types.d.ts +9 -1
  22. package/package.json +1 -1
  23. package/src/components/ui/Chat/Chat.types.test.ts +32 -0
  24. package/src/components/ui/Chat/Chat.types.ts +13 -1
  25. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +24 -46
  26. package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +6 -8
  27. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +2 -0
  28. package/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.ts +11 -2
  29. package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +2 -0
  30. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +147 -109
  31. package/src/components/ui/Chat/index.ts +5 -1
  32. package/src/docs/docsHeaderActions.tsx +3 -2
  33. package/src/docs/pages/ChatAttachmentsDropzonePage.tsx +0 -5
  34. package/src/docs/pages/ChatPage.tsx +0 -5
  35. package/src/docs/pages/ChatSlashCommandsPage.tsx +43 -14
  36. package/src/docs/pages/ChatUserCsvAttachmentPage.tsx +0 -5
  37. package/src/tiptap/slash-mention/createSlashMentionExtension.ts +65 -11
  38. package/src/tiptap/slash-mention/index.ts +1 -0
  39. package/src/tiptap/slash-mention/types.ts +10 -1
@@ -7,7 +7,7 @@ import {
7
7
  } from '#uilib/components/ui/Chat/ChatMessage/presetScript';
8
8
  import { TextShimmer } from '#uilib/components/ui/TextShimmer';
9
9
  import { Scroll } from '@homecode/ui';
10
- import { ChartLineIcon, PaperPlaneRightIcon, X } from '@phosphor-icons/react';
10
+ import { PaperPlaneRightIcon, X } from '@phosphor-icons/react';
11
11
 
12
12
  import { Button } from '../../Button';
13
13
  import { DropZone } from '../../DropZone/DropZone';
@@ -37,13 +37,8 @@ export function ChatChrome({
37
37
  scriptContinueLabel,
38
38
  onScriptContinue,
39
39
  renderMessageChart,
40
- showBranchActionsRow,
41
40
  showSyntheticBranchButtons,
42
41
  unusedBranchKeys,
43
- isScriptComplete,
44
- onGenerateDashboard,
45
- generatingDashboard,
46
- onGenerateDashboardClick,
47
42
  showInlinePresets,
48
43
  isLastMessageFromUser,
49
44
  scrollRef,
@@ -57,6 +52,7 @@ export function ChatChrome({
57
52
  allowPdfAttachments = false,
58
53
  onAttachmentsDropped,
59
54
  slashCommandItems,
55
+ onSlashItemCommand,
60
56
  promptPlaceholder,
61
57
  }: ChatChromeProps) {
62
58
  const filteredAllowedAttachments = useMemo(
@@ -223,48 +219,29 @@ export function ChatChrome({
223
219
  );
224
220
  })}
225
221
 
226
- {showBranchActionsRow && (
222
+ {showSyntheticBranchButtons ? (
227
223
  <div className={S.branchRow}>
228
- {showSyntheticBranchButtons
229
- ? unusedBranchKeys.map(key => {
230
- const label =
231
- displayLabelForBranchKeyFromMessages(
232
- key,
233
- messages,
234
- ) ?? humanizeBranchKey(key);
235
- return (
236
- <span key={key} className={S.branchBtnWrap}>
237
- <Button
238
- type="button"
239
- variant="outline"
240
- size="sm"
241
- disabled={isLoading}
242
- onClick={() => onQuickReply(key, label)}
243
- >
244
- <PaperPlaneRightIcon />
245
- {label}
246
- </Button>
247
- </span>
248
- );
249
- })
250
- : null}
251
-
252
- {isScriptComplete &&
253
- onGenerateDashboard &&
254
- !generatingDashboard ? (
255
- <Button
256
- type="button"
257
- variant="default"
258
- size="lg"
259
- disabled={isLoading}
260
- onClick={onGenerateDashboardClick}
261
- >
262
- <ChartLineIcon />
263
- Generate Dashboard
264
- </Button>
265
- ) : null}
224
+ {unusedBranchKeys.map(key => {
225
+ const label =
226
+ displayLabelForBranchKeyFromMessages(key, messages) ??
227
+ humanizeBranchKey(key);
228
+ return (
229
+ <span key={key} className={S.branchBtnWrap}>
230
+ <Button
231
+ type="button"
232
+ variant="outline"
233
+ size="sm"
234
+ disabled={isLoading}
235
+ onClick={() => onQuickReply(key, label)}
236
+ >
237
+ <PaperPlaneRightIcon />
238
+ {label}
239
+ </Button>
240
+ </span>
241
+ );
242
+ })}
266
243
  </div>
267
- )}
244
+ ) : null}
268
245
 
269
246
  {showInlinePresets && renderPresets('inline')}
270
247
 
@@ -291,6 +268,7 @@ export function ChatChrome({
291
268
  prefillMessage={promptPrefill ?? undefined}
292
269
  placeholder={promptPlaceholder}
293
270
  slashCommandItems={slashCommandItems}
271
+ onSlashItemCommand={onSlashItemCommand}
294
272
  attachmentAccept={
295
273
  attachmentsDropzoneEnabled ? attachmentAccept : undefined
296
274
  }
@@ -6,7 +6,10 @@ import type {
6
6
  } from '#uilib/components/ui/Chat/Chat.types';
7
7
  import type { ChatEmptyStateProps } from '#uilib/components/ui/Chat/ChatEmptyState/ChatEmptyState.types';
8
8
  import type { ChatPresetsLayout } from '#uilib/components/ui/Chat/ChatPresets';
9
- import type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
9
+ import type {
10
+ SlashCommandItem,
11
+ SlashOnItemCommand,
12
+ } from '#uilib/tiptap/slash-mention/types';
10
13
  import type { ScrollRef } from '@homecode/ui';
11
14
 
12
15
  export type ChatChromeResizeHandleConfig = {
@@ -33,15 +36,8 @@ export interface ChatChromeProps {
33
36
  scriptContinueLabel: string | undefined;
34
37
  onScriptContinue: (() => void) | undefined;
35
38
  renderMessageChart?: () => React.ReactNode;
36
- showBranchActionsRow: boolean;
37
39
  showSyntheticBranchButtons: boolean;
38
40
  unusedBranchKeys: string[];
39
- isScriptComplete: boolean;
40
- onGenerateDashboard:
41
- | ((transcript: string) => void | Promise<void>)
42
- | undefined;
43
- generatingDashboard: boolean;
44
- onGenerateDashboardClick: () => void;
45
41
  showInlinePresets: boolean;
46
42
  isLastMessageFromUser: boolean;
47
43
  scrollRef: RefObject<ScrollRef | null>;
@@ -65,6 +61,8 @@ export interface ChatChromeProps {
65
61
  ) => void | Promise<void>;
66
62
  /** Slash menu (`/`), forwarded to `Chat.Prompt`; omit or pass empty list to disable slash palette. */
67
63
  slashCommandItems?: SlashCommandItem[];
64
+ /** Custom slash pick handler; forwarded to `Chat.Prompt`. */
65
+ onSlashItemCommand?: SlashOnItemCommand;
68
66
  /** Composer placeholder forwarded to `Chat.Prompt`. */
69
67
  promptPlaceholder?: string;
70
68
  }
@@ -14,6 +14,7 @@ export function ChatPrompt({
14
14
  footer,
15
15
  prefillMessage,
16
16
  slashCommandItems,
17
+ onSlashItemCommand,
17
18
  attachments = [],
18
19
  onRemoveAttachment,
19
20
  disabled = false,
@@ -28,6 +29,7 @@ export function ChatPrompt({
28
29
  disabled,
29
30
  placeholder,
30
31
  slashCommandItems,
32
+ onSlashItemCommand,
31
33
  prefillMessage,
32
34
  attachmentsCount,
33
35
  onEnterSubmit: () => emitSubmitRef.current(),
@@ -12,7 +12,10 @@ import {
12
12
  DEFAULT_CHAT_SLASH_ITEMS,
13
13
  createSlashMentionExtension,
14
14
  } from '#uilib/tiptap/slash-mention';
15
- import type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
15
+ import type {
16
+ SlashCommandItem,
17
+ SlashOnItemCommand,
18
+ } from '#uilib/tiptap/slash-mention/types';
16
19
  import type { AnyExtension, Editor } from '@tiptap/core';
17
20
  import { Placeholder } from '@tiptap/extensions';
18
21
  import { useEditor } from '@tiptap/react';
@@ -28,6 +31,7 @@ export type UseChatPromptEditorOptions = {
28
31
  disabled: boolean;
29
32
  placeholder?: string;
30
33
  slashCommandItems?: SlashCommandItem[];
34
+ onSlashItemCommand?: SlashOnItemCommand;
31
35
  prefillMessage?: string | null;
32
36
  /** Staged attachment count — Enter-to-send when text empty but files present. */
33
37
  attachmentsCount?: number;
@@ -46,11 +50,14 @@ export function useChatPromptEditor({
46
50
  disabled,
47
51
  placeholder,
48
52
  slashCommandItems,
53
+ onSlashItemCommand,
49
54
  prefillMessage,
50
55
  attachmentsCount = 0,
51
56
  onEnterSubmit,
52
57
  }: UseChatPromptEditorOptions): UseChatPromptEditorResult {
53
58
  const slashOpenRef = useRef(false);
59
+ const onSlashItemCommandRef = useRef(onSlashItemCommand);
60
+ onSlashItemCommandRef.current = onSlashItemCommand;
54
61
  const suggestionActiveUpdater = useCallback((active: boolean) => {
55
62
  slashOpenRef.current = active;
56
63
  }, []);
@@ -94,7 +101,9 @@ export function useChatPromptEditor({
94
101
  exts.push(
95
102
  createSlashMentionExtension({
96
103
  items: slashItemsStable,
104
+ suggestionPlacement: 'above',
97
105
  onSuggestionUiActiveChange: suggestionActiveUpdater,
106
+ onItemCommand: ctx => onSlashItemCommandRef.current?.(ctx) === true,
98
107
  }),
99
108
  );
100
109
  }
@@ -138,7 +147,7 @@ export function useChatPromptEditor({
138
147
  },
139
148
  onCreate: bindEditorDom,
140
149
  },
141
- [extensions, disabled, bindEditorDom, ariaLabelComposer],
150
+ [extensions, bindEditorDom, ariaLabelComposer],
142
151
  );
143
152
 
144
153
  useEffect(() => {
@@ -47,6 +47,7 @@ export function ChatSheet({
47
47
  allowedAttachments,
48
48
  allowPdfAttachments,
49
49
  onAttachmentsDropped,
50
+ slashCommandItems,
50
51
  inline = false,
51
52
  }: ChatSheetProps) {
52
53
  const model = useChatPanelChromeModel({
@@ -61,6 +62,7 @@ export function ChatSheet({
61
62
  allowedAttachments,
62
63
  allowPdfAttachments,
63
64
  onAttachmentsDropped,
65
+ slashCommandItems,
64
66
  });
65
67
 
66
68
  if (actionsRef) {
@@ -2,9 +2,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
3
  import {
4
4
  ChatPreset,
5
+ GENERATE_DASHBOARD_SLASH_COMMAND_ID,
5
6
  GENERATING_DASHBOARD_SYSTEM_TEXT,
6
7
  MessageRole,
7
8
  type ScriptCompletePayload,
9
+ isGenerateDashboardSlashMessage,
8
10
  } from '#uilib/components/ui/Chat/Chat.types';
9
11
  import {
10
12
  branchKeysUsedByUserMessages,
@@ -38,6 +40,10 @@ import useEvent from '#uilib/hooks/useEvent';
38
40
  import { useIsMobile } from '#uilib/hooks/useIsMobile';
39
41
  import { useQueryParams } from '#uilib/hooks/useQueryParams';
40
42
  import logger from '#uilib/lib/logger';
43
+ import type {
44
+ SlashCommandItem,
45
+ SlashItemCommandContext,
46
+ } from '#uilib/tiptap/slash-mention/types';
41
47
  import { mergePresetFreeformDefaults } from '#uilib/utils/chatPresetMerge';
42
48
  import { ScrollRef } from '@homecode/ui';
43
49
 
@@ -57,7 +63,7 @@ export type UseChatPanelChromeModelInput = {
57
63
  onMessage?: (message: string) => void;
58
64
  /** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
59
65
  onScriptComplete?: (payload: ScriptCompletePayload) => void;
60
- /** After intake script completes, user can generate a dashboard from the full transcript (e.g. Decision board). */
66
+ /** Generate dashboard from chat transcript (e.g. via `/generate-dashboard` slash command). */
61
67
  onGenerateDashboard?: (transcript: string) => void | Promise<void>;
62
68
  /** Renders `[CHART]` tokens in assistant messages. */
63
69
  renderMessageChart?: () => React.ReactNode;
@@ -70,6 +76,8 @@ export type UseChatPanelChromeModelInput = {
70
76
  onAttachmentsDropped?: (
71
77
  items: ChatAttachmentDropItem[],
72
78
  ) => void | Promise<void>;
79
+ /** Slash menu (`/`) in the composer; omit or pass empty to disable. */
80
+ slashCommandItems?: SlashCommandItem[];
73
81
  };
74
82
 
75
83
  export type UseChatPanelChromeModelResult = {
@@ -118,6 +126,7 @@ export function useChatPanelChromeModel({
118
126
  allowedAttachments,
119
127
  allowPdfAttachments,
120
128
  onAttachmentsDropped,
129
+ slashCommandItems,
121
130
  }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult {
122
131
  const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
123
132
  const isMobile = useIsMobile();
@@ -189,7 +198,7 @@ export function useChatPanelChromeModel({
189
198
  const [intakeByChatId, setIntakeByChatId] = useState<
190
199
  Record<string, IntakeScriptState | undefined>
191
200
  >({});
192
- /** Preset intake finished for this session; show [Generate Dashboard] when `onGenerateDashboard` is set. */
201
+ /** Preset intake finished for this session (e.g. `onScriptComplete` callback). */
193
202
  const [scriptCompleteByChatId, setScriptCompleteByChatId] = useState<
194
203
  Record<string, boolean>
195
204
  >({});
@@ -616,103 +625,107 @@ export function useChatPanelChromeModel({
616
625
  ],
617
626
  );
618
627
 
619
- const submitPreset = useCallback(async (preset: ChatPreset) => {
620
- const script = preset.script;
621
- const scriptGraph = isPresetScriptGraph(script);
622
- const hasLinearScript = Array.isArray(script) && script.length > 0;
623
- const hasReplies = preset.replies && Object.keys(preset.replies).length > 0;
624
- const isLocalDemo =
625
- hasLinearScript ||
626
- scriptGraph ||
627
- Boolean(preset.answer?.trim()) ||
628
- Boolean(hasReplies);
629
-
630
- if (!isLocalDemo) {
631
- if (!currentChatId) return;
632
- endLocalDemoFlow(currentChatId);
633
- await handlePromptSubmit(preset.text);
634
- return;
635
- }
628
+ const submitPreset = useCallback(
629
+ async (preset: ChatPreset) => {
630
+ const script = preset.script;
631
+ const scriptGraph = isPresetScriptGraph(script);
632
+ const hasLinearScript = Array.isArray(script) && script.length > 0;
633
+ const hasReplies =
634
+ preset.replies && Object.keys(preset.replies).length > 0;
635
+ const isLocalDemo =
636
+ hasLinearScript ||
637
+ scriptGraph ||
638
+ Boolean(preset.answer?.trim()) ||
639
+ Boolean(hasReplies);
640
+
641
+ if (!isLocalDemo) {
642
+ if (!currentChatId) return;
643
+ endLocalDemoFlow(currentChatId);
644
+ await handlePromptSubmit(preset.text);
645
+ return;
646
+ }
636
647
 
637
- setLocalUiBusy(true);
638
- try {
639
- if (!currentChatId) return;
640
- setScriptCompleteByChatId(prev => {
641
- const next = { ...prev };
642
- delete next[currentChatId];
643
- return next;
644
- });
645
- addMessage(currentChatId, MessageRole.USER, preset.text);
646
- await new Promise(resolve => setTimeout(resolve, 1000));
647
- const firstAssistant = scriptGraph
648
- ? script.initialMessage
649
- : (preset.answer ?? '');
650
- addMessage(currentChatId, MessageRole.ASSISTANT, firstAssistant);
651
-
652
- setScriptByChatId(prev => {
653
- const next = { ...prev };
654
- if (hasLinearScript) {
655
- next[currentChatId] = {
656
- lines: script,
657
- nextIndex: 0,
658
- presetId: preset.id,
659
- };
660
- } else {
648
+ setLocalUiBusy(true);
649
+ try {
650
+ if (!currentChatId) return;
651
+ setScriptCompleteByChatId(prev => {
652
+ const next = { ...prev };
661
653
  delete next[currentChatId];
654
+ return next;
655
+ });
656
+ addMessage(currentChatId, MessageRole.USER, preset.text);
657
+ await new Promise(resolve => setTimeout(resolve, 1000));
658
+ const firstAssistant = scriptGraph
659
+ ? script.initialMessage
660
+ : (preset.answer ?? '');
661
+ addMessage(currentChatId, MessageRole.ASSISTANT, firstAssistant);
662
+
663
+ setScriptByChatId(prev => {
664
+ const next = { ...prev };
665
+ if (hasLinearScript) {
666
+ next[currentChatId] = {
667
+ lines: script,
668
+ nextIndex: 0,
669
+ presetId: preset.id,
670
+ };
671
+ } else {
672
+ delete next[currentChatId];
673
+ }
674
+ return next;
675
+ });
676
+ setQuickReplyBranchesByChat(prev => {
677
+ const next = { ...prev };
678
+ if (scriptGraph) {
679
+ next[currentChatId] = branchesFromPresetScriptGraph(script);
680
+ } else if (hasReplies && !hasLinearScript) {
681
+ next[currentChatId] = preset.replies!;
682
+ } else {
683
+ delete next[currentChatId];
684
+ }
685
+ return next;
686
+ });
687
+ if (scriptGraph || (hasReplies && !hasLinearScript)) {
688
+ setUsedScriptBranchKeysByChat(prev => ({
689
+ ...prev,
690
+ [currentChatId]: [],
691
+ }));
662
692
  }
663
- return next;
664
- });
665
- setQuickReplyBranchesByChat(prev => {
666
- const next = { ...prev };
667
693
  if (scriptGraph) {
668
- next[currentChatId] = branchesFromPresetScriptGraph(script);
669
- } else if (hasReplies && !hasLinearScript) {
670
- next[currentChatId] = preset.replies!;
671
- } else {
672
- delete next[currentChatId];
673
- }
674
- return next;
675
- });
676
- if (scriptGraph || (hasReplies && !hasLinearScript)) {
677
- setUsedScriptBranchKeysByChat(prev => ({
678
- ...prev,
679
- [currentChatId]: [],
680
- }));
681
- }
682
- if (scriptGraph) {
683
- const fromMerged = presetsWithFreeform.find(p => p.id === preset.id);
684
- let freeformNext = {
685
- ...(fromMerged?.freeformNext ?? {}),
686
- ...(preset.freeformNext ?? {}),
687
- };
688
- if (Object.keys(freeformNext).length === 0) {
689
- const ks = Object.keys(branchesFromPresetScriptGraph(script));
690
- if (ks.length === 1) {
691
- freeformNext = { initial: ks[0] };
694
+ const fromMerged = presetsWithFreeform.find(p => p.id === preset.id);
695
+ let freeformNext = {
696
+ ...(fromMerged?.freeformNext ?? {}),
697
+ ...(preset.freeformNext ?? {}),
698
+ };
699
+ if (Object.keys(freeformNext).length === 0) {
700
+ const ks = Object.keys(branchesFromPresetScriptGraph(script));
701
+ if (ks.length === 1) {
702
+ freeformNext = { initial: ks[0] };
703
+ }
692
704
  }
705
+ setIntakeByChatId(prev => ({
706
+ ...prev,
707
+ [currentChatId!]: {
708
+ presetId: preset.id,
709
+ freeformNext,
710
+ scriptStepId: 'initial',
711
+ answers: {},
712
+ },
713
+ }));
693
714
  }
694
- setIntakeByChatId(prev => ({
695
- ...prev,
696
- [currentChatId!]: {
697
- presetId: preset.id,
698
- freeformNext,
699
- scriptStepId: 'initial',
700
- answers: {},
701
- },
702
- }));
715
+ } catch (error) {
716
+ logger.error('Error sending chat message:', error);
717
+ } finally {
718
+ setLocalUiBusy(false);
703
719
  }
704
- } catch (error) {
705
- logger.error('Error sending chat message:', error);
706
- } finally {
707
- setLocalUiBusy(false);
708
- }
709
- }, [
710
- currentChatId,
711
- endLocalDemoFlow,
712
- handlePromptSubmit,
713
- addMessage,
714
- presetsWithFreeform,
715
- ]);
720
+ },
721
+ [
722
+ currentChatId,
723
+ endLocalDemoFlow,
724
+ handlePromptSubmit,
725
+ addMessage,
726
+ presetsWithFreeform,
727
+ ],
728
+ );
716
729
 
717
730
  const resolvedEmptyState = useMemo((): ChatEmptyStateProps | undefined => {
718
731
  if (!emptyState) return undefined;
@@ -948,15 +961,6 @@ export function useChatPanelChromeModel({
948
961
  !linearScriptActive &&
949
962
  (!graphActive || (!lastHasQuickMarkers && unusedBranchKeys.length === 0));
950
963
 
951
- const isScriptComplete = Boolean(
952
- currentChatId && scriptCompleteByChatId[currentChatId],
953
- );
954
-
955
- /** Branch row also when intake is done but all branches used (Generate Dashboard only). */
956
- const showBranchActionsRow =
957
- showSyntheticBranchButtons ||
958
- (isScriptComplete && Boolean(onGenerateDashboard) && !generatingDashboard);
959
-
960
964
  const handleGenerateDashboard = useCallback(async () => {
961
965
  if (!currentChatId || !onGenerateDashboard) return;
962
966
  const transcript = formatChatTranscript(
@@ -989,6 +993,45 @@ export function useChatPanelChromeModel({
989
993
  removeMessageById,
990
994
  ]);
991
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
+ const onPromptSubmitWithSlashCommands = useCallback(
1016
+ async (message: string, attachments?: ChatAttachmentDropItem[]) => {
1017
+ if (
1018
+ isGenerateDashboardSlashMessage(message) &&
1019
+ onGenerateDashboard &&
1020
+ !generatingDashboard
1021
+ ) {
1022
+ void handleGenerateDashboard();
1023
+ return;
1024
+ }
1025
+ return handlePromptSubmit(message, attachments);
1026
+ },
1027
+ [
1028
+ handlePromptSubmit,
1029
+ onGenerateDashboard,
1030
+ generatingDashboard,
1031
+ handleGenerateDashboard,
1032
+ ],
1033
+ );
1034
+
992
1035
  const onDragChatWidth = useCallback(
993
1036
  (rawPx: number) => setChatWidthPx(rawPx, { persist: false }),
994
1037
  [setChatWidthPx],
@@ -1020,26 +1063,21 @@ export function useChatPanelChromeModel({
1020
1063
  scriptContinueLabel,
1021
1064
  onScriptContinue,
1022
1065
  renderMessageChart,
1023
- showBranchActionsRow,
1024
1066
  showSyntheticBranchButtons,
1025
1067
  unusedBranchKeys,
1026
- isScriptComplete,
1027
- onGenerateDashboard,
1028
- generatingDashboard,
1029
- onGenerateDashboardClick: () => {
1030
- void handleGenerateDashboard();
1031
- },
1032
1068
  showInlinePresets,
1033
1069
  isLastMessageFromUser,
1034
1070
  scrollRef,
1035
1071
  effectiveScopeId,
1036
- onPromptSubmit: handlePromptSubmit,
1072
+ onPromptSubmit: onPromptSubmitWithSlashCommands,
1037
1073
  onChatDeleted: endLocalDemoFlow,
1038
1074
  promptPrefill: promptLinkPrefill,
1039
1075
  emptyState: resolvedEmptyState,
1040
1076
  allowedAttachments,
1041
1077
  allowPdfAttachments,
1042
1078
  onAttachmentsDropped,
1079
+ slashCommandItems,
1080
+ onSlashItemCommand,
1043
1081
  };
1044
1082
 
1045
1083
  const toggleOpen = () => onOpenChange(!isOpen);
@@ -39,6 +39,10 @@ export type {
39
39
  Message,
40
40
  UserTextFileAttachment,
41
41
  } from './Chat.types';
42
- export { MessageRole } from './Chat.types';
42
+ export {
43
+ GENERATE_DASHBOARD_SLASH_COMMAND_ID,
44
+ isGenerateDashboardSlashMessage,
45
+ MessageRole,
46
+ } from './Chat.types';
43
47
  export type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
44
48
  export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
@@ -1,9 +1,9 @@
1
- import { ChatSheet } from '#uilib/components/ui/Chat';
1
+ import { ChatSheet, ChatSheetProps } from '#uilib/components/ui/Chat';
2
2
  import { MessageSquare } from 'lucide-react';
3
3
 
4
4
  import { DOCS_CHAT_SCOPE_ID } from './docsConstants';
5
5
 
6
- export function DocsHeaderActions() {
6
+ export function DocsHeaderActions(props: ChatSheetProps) {
7
7
  return (
8
8
  <ChatSheet
9
9
  triggerLabel={
@@ -21,6 +21,7 @@ export function DocsHeaderActions() {
21
21
  <p>Optional empty-state slot via additionalContent.</p>
22
22
  ),
23
23
  }}
24
+ {...props}
24
25
  />
25
26
  );
26
27
  }
@@ -120,13 +120,8 @@ export default function ChatAttachmentsDropzonePage() {
120
120
  isLoading={isLoading}
121
121
  scriptContinueLabel={undefined}
122
122
  onScriptContinue={undefined}
123
- showBranchActionsRow={false}
124
123
  showSyntheticBranchButtons={false}
125
124
  unusedBranchKeys={[]}
126
- isScriptComplete={false}
127
- onGenerateDashboard={undefined}
128
- generatingDashboard={false}
129
- onGenerateDashboardClick={() => {}}
130
125
  showInlinePresets={false}
131
126
  isLastMessageFromUser={isLastMessageFromUser}
132
127
  scrollRef={scrollRef}
@@ -84,13 +84,8 @@ export default function ChatPage() {
84
84
  isLoading={isLoading}
85
85
  scriptContinueLabel={undefined}
86
86
  onScriptContinue={undefined}
87
- showBranchActionsRow={false}
88
87
  showSyntheticBranchButtons={false}
89
88
  unusedBranchKeys={[]}
90
- isScriptComplete={false}
91
- onGenerateDashboard={undefined}
92
- generatingDashboard={false}
93
- onGenerateDashboardClick={() => {}}
94
89
  showInlinePresets={false}
95
90
  isLastMessageFromUser={isLastMessageFromUser}
96
91
  scrollRef={scrollRef}