@paymanai/payman-ask-sdk 4.0.0 → 4.0.2

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.mjs CHANGED
@@ -123,6 +123,36 @@ function subscribeToCfRay(urlPattern, listener) {
123
123
  };
124
124
  }
125
125
 
126
+ // src/utils/slashCommands.ts
127
+ var DEFAULT_SLASH_COMMANDS = [
128
+ {
129
+ name: "/draft-knowledge",
130
+ description: "Create a Knowledge Vault draft for admin review",
131
+ requiredAnyPermissions: ["vault:author", "vault:publish"]
132
+ }
133
+ ];
134
+ function filterSlashCommands(commands, permissions) {
135
+ const granted = new Set(permissions ?? []);
136
+ return commands.filter((command) => {
137
+ const required = command.requiredAnyPermissions ?? [];
138
+ return required.length === 0 || required.some((permission) => granted.has(permission));
139
+ });
140
+ }
141
+ function parseSlashCommand(content) {
142
+ const trimmedStart = content.trimStart();
143
+ if (!trimmedStart.startsWith("/")) return null;
144
+ const match = trimmedStart.match(/^(\/[a-z][a-z0-9-]*)(?:\s+([\s\S]*))?$/i);
145
+ if (!match) return null;
146
+ return {
147
+ command: match[1],
148
+ body: match[2] ?? ""
149
+ };
150
+ }
151
+ function slashCommandBodyIsEmpty(content) {
152
+ const parsed = parseSlashCommand(content);
153
+ return parsed?.command === "/draft-knowledge" && parsed.body.trim().length === 0;
154
+ }
155
+
126
156
  // src/utils/errorMessages.ts
127
157
  var WORKFLOW_FAILED = "WORKFLOW_FAILED";
128
158
  var STREAM_NOT_STARTED = "STREAM_NOT_STARTED";
@@ -763,6 +793,7 @@ function UserMessage({
763
793
  animated = false,
764
794
  showAvatar = false
765
795
  }) {
796
+ const parsedCommand = parseSlashCommand(message.content);
766
797
  const content = /* @__PURE__ */ jsxs("div", { className: "flex gap-3 justify-end w-full", children: [
767
798
  /* @__PURE__ */ jsxs("div", { className: "max-w-[80%] min-w-0 flex flex-col items-end", children: [
768
799
  /* @__PURE__ */ jsx(
@@ -773,7 +804,10 @@ function UserMessage({
773
804
  "bg-primary text-primary-foreground",
774
805
  "shadow-sm"
775
806
  ),
776
- children: /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed whitespace-pre-wrap break-words", children: message.content })
807
+ children: parsedCommand ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
808
+ /* @__PURE__ */ jsx("span", { className: "inline-flex rounded-md bg-black/15 px-2 py-0.5 font-mono text-xs", children: parsedCommand.command }),
809
+ parsedCommand.body.trim() ? /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed whitespace-pre-wrap break-words", children: parsedCommand.body }) : null
810
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed whitespace-pre-wrap break-words", children: message.content })
777
811
  }
778
812
  ),
779
813
  /* @__PURE__ */ jsx("span", { className: "text-[10px] mt-1.5 mr-1 text-muted-foreground/70 select-none", children: formatDate(message.timestamp) })
@@ -914,7 +948,7 @@ function MessageList({
914
948
  messages,
915
949
  isLoading = false,
916
950
  emptyStateText = "What can I help with?",
917
- showEmptyStateIcon = true,
951
+ showEmptyStateIcon: _showEmptyStateIcon = true,
918
952
  emptyStateComponent,
919
953
  layout = "full-width",
920
954
  showTimestamps = false,
@@ -1289,6 +1323,7 @@ function UserMessageV2({
1289
1323
  }
1290
1324
  };
1291
1325
  const timestamp = formatMessageTime(message.timestamp);
1326
+ const parsedCommand = parseSlashCommand(message.content);
1292
1327
  const hasVisibleActions = showCopyAction || showEditAction && !!onEdit || showRetryAction && !!onRetry;
1293
1328
  const toastPortal = typeof document !== "undefined" ? createPortal(
1294
1329
  /* @__PURE__ */ jsx(AnimatePresence, { children: toast && /* @__PURE__ */ jsx(
@@ -1333,7 +1368,10 @@ function UserMessageV2({
1333
1368
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1334
1369
  toastPortal,
1335
1370
  /* @__PURE__ */ jsx("div", { className: "payman-v2-user-msg payman-v2-fade-in", children: /* @__PURE__ */ jsxs("div", { className: "payman-v2-user-msg-group", children: [
1336
- /* @__PURE__ */ jsx("div", { className: "payman-v2-user-msg-bubble", children: /* @__PURE__ */ jsx("p", { className: "payman-v2-user-msg-text", children: message.content }) }),
1371
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-user-msg-bubble", children: parsedCommand ? /* @__PURE__ */ jsxs("div", { className: "payman-v2-user-msg-command", children: [
1372
+ /* @__PURE__ */ jsx("span", { className: "payman-v2-user-msg-command-chip", children: parsedCommand.command }),
1373
+ parsedCommand.body.trim() ? /* @__PURE__ */ jsx("p", { className: "payman-v2-user-msg-text", children: parsedCommand.body }) : null
1374
+ ] }) : /* @__PURE__ */ jsx("p", { className: "payman-v2-user-msg-text", children: message.content }) }),
1337
1375
  message.isError && message.errorDetails && (() => {
1338
1376
  const resolvedError = getConflictErrorMessage(message.errorDetails) ?? message.errorDetails;
1339
1377
  return /* @__PURE__ */ jsxs("div", { className: "payman-v2-user-msg-error", children: [
@@ -2471,7 +2509,6 @@ var MessageListV2 = forwardRef(
2471
2509
  var ChatInputV2 = forwardRef(
2472
2510
  function ChatInputV22({
2473
2511
  onSend,
2474
- onCancel,
2475
2512
  disabled = false,
2476
2513
  isStreaming = false,
2477
2514
  placeholder = "Reply...",
@@ -2490,12 +2527,16 @@ var ChatInputV2 = forwardRef(
2490
2527
  editingMessageId = null,
2491
2528
  onClearEditing,
2492
2529
  analysisMode,
2493
- onAnalysisModeChange
2530
+ onAnalysisModeChange,
2531
+ slashCommands = []
2494
2532
  }, ref) {
2495
2533
  const [value, setValue] = useState("");
2496
2534
  const [isFocused, setIsFocused] = useState(false);
2497
2535
  const [showActions, setShowActions] = useState(false);
2498
2536
  const [showVoiceTooltip, setShowVoiceTooltip] = useState(false);
2537
+ const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
2538
+ const [inlineHint, setInlineHint] = useState(null);
2539
+ const [commandMenuDismissed, setCommandMenuDismissed] = useState(false);
2499
2540
  const textareaRef = useRef(null);
2500
2541
  const actionsRef = useRef(null);
2501
2542
  const preRecordTextRef = useRef("");
@@ -2508,6 +2549,11 @@ var ChatInputV2 = forwardRef(
2508
2549
  const showAttachmentMenuButton = showAttachmentButton && hasAttachmentOptions;
2509
2550
  const showVoiceButton = enableVoice && onVoicePress != null;
2510
2551
  const isVoiceButtonDisabled = disabled || !voiceAvailable;
2552
+ const commandQuery = value.startsWith("/") && !value.includes("\n") && !/\s/.test(value) ? value.toLowerCase() : null;
2553
+ const commandSuggestions = commandQuery == null ? [] : slashCommands.filter(
2554
+ (command) => command.name.toLowerCase().startsWith(commandQuery)
2555
+ );
2556
+ const showCommandSuggestions = !commandMenuDismissed && !isRecording && !disabled && commandSuggestions.length > 0;
2511
2557
  useEffect(() => {
2512
2558
  if (textareaRef.current) {
2513
2559
  textareaRef.current.style.height = "auto";
@@ -2557,6 +2603,10 @@ var ChatInputV2 = forwardRef(
2557
2603
  setShowActions(false);
2558
2604
  }
2559
2605
  }, [showAttachmentMenuButton]);
2606
+ useEffect(() => {
2607
+ setSelectedCommandIndex(0);
2608
+ setCommandMenuDismissed(false);
2609
+ }, [commandQuery]);
2560
2610
  useEffect(() => {
2561
2611
  return () => {
2562
2612
  if (voiceTooltipTimerRef.current) {
@@ -2572,8 +2622,13 @@ var ChatInputV2 = forwardRef(
2572
2622
  }, [isRecording, transcribedText]);
2573
2623
  const handleSend = useCallback(() => {
2574
2624
  if (!value.trim() || disabled) return;
2625
+ if (slashCommandBodyIsEmpty(value)) {
2626
+ setInlineHint("Add markdown content below the command line.");
2627
+ return;
2628
+ }
2575
2629
  voiceDraftSyncActiveRef.current = false;
2576
2630
  preRecordTextRef.current = "";
2631
+ setInlineHint(null);
2577
2632
  onClearEditing?.();
2578
2633
  onSend(value.trim());
2579
2634
  setValue("");
@@ -2584,7 +2639,48 @@ var ChatInputV2 = forwardRef(
2584
2639
  }
2585
2640
  });
2586
2641
  }, [value, disabled, onClearEditing, onSend]);
2642
+ const selectCommand = useCallback(
2643
+ (command) => {
2644
+ const insertText = command.insertText ?? `${command.name} `;
2645
+ setValue(insertText);
2646
+ setInlineHint(null);
2647
+ setShowActions(false);
2648
+ requestAnimationFrame(() => {
2649
+ const textarea = textareaRef.current;
2650
+ if (!textarea) return;
2651
+ textarea.focus();
2652
+ textarea.setSelectionRange(insertText.length, insertText.length);
2653
+ });
2654
+ },
2655
+ []
2656
+ );
2587
2657
  const handleKeyDown = (e) => {
2658
+ if (showCommandSuggestions) {
2659
+ if (e.key === "ArrowDown") {
2660
+ e.preventDefault();
2661
+ setSelectedCommandIndex(
2662
+ (current) => Math.min(current + 1, commandSuggestions.length - 1)
2663
+ );
2664
+ return;
2665
+ }
2666
+ if (e.key === "ArrowUp") {
2667
+ e.preventDefault();
2668
+ setSelectedCommandIndex((current) => Math.max(current - 1, 0));
2669
+ return;
2670
+ }
2671
+ if (e.key === "Enter" || e.key === "Tab") {
2672
+ e.preventDefault();
2673
+ const command = commandSuggestions[selectedCommandIndex];
2674
+ if (command) selectCommand(command);
2675
+ return;
2676
+ }
2677
+ if (e.key === "Escape") {
2678
+ e.preventDefault();
2679
+ setSelectedCommandIndex(0);
2680
+ setCommandMenuDismissed(true);
2681
+ return;
2682
+ }
2683
+ }
2588
2684
  if (e.key === "Enter" && !e.shiftKey) {
2589
2685
  e.preventDefault();
2590
2686
  if (isStreaming) return;
@@ -2629,6 +2725,35 @@ var ChatInputV2 = forwardRef(
2629
2725
  const canSend = value.trim().length > 0 && !disabled;
2630
2726
  const sendDisabled = !canSend || isStreaming;
2631
2727
  return /* @__PURE__ */ jsxs("div", { className: "payman-v2-input-container", children: [
2728
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showCommandSuggestions && /* @__PURE__ */ jsx(
2729
+ motion.div,
2730
+ {
2731
+ initial: { opacity: 0, y: 6, scale: 0.98 },
2732
+ animate: { opacity: 1, y: 0, scale: 1 },
2733
+ exit: { opacity: 0, y: 6, scale: 0.98 },
2734
+ transition: { duration: 0.12 },
2735
+ className: "payman-v2-command-menu",
2736
+ children: commandSuggestions.map((command, index) => /* @__PURE__ */ jsxs(
2737
+ "button",
2738
+ {
2739
+ type: "button",
2740
+ onMouseDown: (event) => {
2741
+ event.preventDefault();
2742
+ selectCommand(command);
2743
+ },
2744
+ className: cn(
2745
+ "payman-v2-command-option",
2746
+ index === selectedCommandIndex && "payman-v2-command-option-active"
2747
+ ),
2748
+ children: [
2749
+ /* @__PURE__ */ jsx("span", { className: "payman-v2-command-name", children: command.name }),
2750
+ /* @__PURE__ */ jsx("span", { className: "payman-v2-command-description", children: command.description })
2751
+ ]
2752
+ },
2753
+ command.name
2754
+ ))
2755
+ }
2756
+ ) }),
2632
2757
  /* @__PURE__ */ jsxs(
2633
2758
  "div",
2634
2759
  {
@@ -2673,6 +2798,8 @@ var ChatInputV2 = forwardRef(
2673
2798
  if (isRecording) return;
2674
2799
  const nextValue = e.target.value;
2675
2800
  voiceDraftSyncActiveRef.current = false;
2801
+ setInlineHint(null);
2802
+ setCommandMenuDismissed(false);
2676
2803
  setValue(nextValue);
2677
2804
  if (editingMessageId && nextValue.length === 0) {
2678
2805
  onClearEditing?.();
@@ -2887,6 +3014,7 @@ var ChatInputV2 = forwardRef(
2887
3014
  ]
2888
3015
  }
2889
3016
  ),
3017
+ inlineHint && /* @__PURE__ */ jsx("p", { className: "payman-v2-input-inline-hint", children: inlineHint }),
2890
3018
  /* @__PURE__ */ jsx("p", { className: "payman-v2-input-disclaimer", children: "AI can make mistakes. Please double-check responses." })
2891
3019
  ] });
2892
3020
  }
@@ -3353,7 +3481,10 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3353
3481
  showAttachmentButton = true,
3354
3482
  showUploadImageButton = true,
3355
3483
  showAttachFileButton = true,
3356
- messageActions: messageActionsConfig
3484
+ messageActions: messageActionsConfig,
3485
+ enableSlashCommands = true,
3486
+ slashCommands: slashCommandsConfig,
3487
+ commandPermissions
3357
3488
  } = config;
3358
3489
  const messageActions = useMemo(
3359
3490
  () => ({
@@ -3371,6 +3502,13 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3371
3502
  }),
3372
3503
  [messageActionsConfig]
3373
3504
  );
3505
+ const slashCommands = useMemo(
3506
+ () => enableSlashCommands ? filterSlashCommands(
3507
+ slashCommandsConfig ?? DEFAULT_SLASH_COMMANDS,
3508
+ commandPermissions
3509
+ ) : [],
3510
+ [commandPermissions, enableSlashCommands, slashCommandsConfig]
3511
+ );
3374
3512
  const isSessionParamsConfigured = useMemo(() => {
3375
3513
  if (!sessionParams) return false;
3376
3514
  return !!(sessionParams.id?.trim() && sessionParams.name?.trim());
@@ -3568,7 +3706,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3568
3706
  editingMessageId,
3569
3707
  onClearEditing: handleClearEditing,
3570
3708
  analysisMode,
3571
- onAnalysisModeChange: setAnalysisMode
3709
+ onAnalysisModeChange: setAnalysisMode,
3710
+ slashCommands
3572
3711
  }
3573
3712
  )
3574
3713
  }
@@ -3643,7 +3782,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
3643
3782
  editingMessageId,
3644
3783
  onClearEditing: handleClearEditing,
3645
3784
  analysisMode,
3646
- onAnalysisModeChange: setAnalysisMode
3785
+ onAnalysisModeChange: setAnalysisMode,
3786
+ slashCommands
3647
3787
  }
3648
3788
  )
3649
3789
  ]