diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index 2d55f4d20..f5de0be7d 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -499,10 +499,14 @@ export const InlineMentionEditor = forwardRef 0) { const range = selection.getRangeAt(0); @@ -512,63 +516,41 @@ export const InlineMentionEditor = forwardRef= 0; i--) { - if (textContent[i] === "@") { - atIndex = i; - break; - } - // Stop if we hit a space (@ must be at word boundary) if (textContent[i] === " " || textContent[i] === "\n") { + wordStart = i + 1; break; } } - if (atIndex !== -1) { - const query = textContent.slice(atIndex + 1, cursorPos); - // Only trigger if query doesn't start with space + let triggerChar: "@" | "/" | null = null; + let triggerIndex = -1; + for (let i = wordStart; i < cursorPos; i++) { + if (textContent[i] === "@" || textContent[i] === "/") { + triggerChar = textContent[i] as "@" | "/"; + triggerIndex = i; + break; + } + } + + if (triggerChar === "@" && triggerIndex !== -1) { + const query = textContent.slice(triggerIndex + 1, cursorPos); if (!query.startsWith(" ")) { shouldTriggerMention = true; mentionQuery = query; } - } - } - } - - // Check for / actions (same pattern as @) - let shouldTriggerAction = false; - let actionQuery = ""; - - if (!shouldTriggerMention && selection && selection.rangeCount > 0) { - const range = selection.getRangeAt(0); - const textNode = range.startContainer; - - if (textNode.nodeType === Node.TEXT_NODE) { - const textContent = textNode.textContent || ""; - const cursorPos = range.startOffset; - - let slashIndex = -1; - for (let i = cursorPos - 1; i >= 0; i--) { - if (textContent[i] === "/") { - slashIndex = i; - break; - } - if (textContent[i] === " " || textContent[i] === "\n") { - break; - } - } - - if ( - slashIndex !== -1 && - (slashIndex === 0 || - textContent[slashIndex - 1] === " " || - textContent[slashIndex - 1] === "\n") - ) { - const query = textContent.slice(slashIndex + 1, cursorPos); - if (!query.startsWith(" ")) { - shouldTriggerAction = true; - actionQuery = query; + } else if (triggerChar === "/" && triggerIndex !== -1) { + if ( + triggerIndex === 0 || + textContent[triggerIndex - 1] === " " || + textContent[triggerIndex - 1] === "\n" + ) { + const query = textContent.slice(triggerIndex + 1, cursorPos); + if (!query.startsWith(" ")) { + shouldTriggerAction = true; + actionQuery = query; + } } } } diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 9c6521f31..a087463ba 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -394,12 +394,6 @@ export const DocumentMentionPicker = forwardRef< [selectableDocuments, highlightedIndex, handleSelectDocument, onDone] ); - // Hide popup when there are no documents to display (regardless of fetch state) - // Search continues in background; popup reappears when results arrive - if (!actualLoading && actualDocuments.length === 0) { - return null; - } - return (
)}
- ) : null} + ) : ( +
+

No matching documents

+
+ )} ); diff --git a/surfsense_web/components/new-chat/prompt-picker.tsx b/surfsense_web/components/new-chat/prompt-picker.tsx index 3e6457b8c..d3166cad1 100644 --- a/surfsense_web/components/new-chat/prompt-picker.tsx +++ b/surfsense_web/components/new-chat/prompt-picker.tsx @@ -15,7 +15,7 @@ import { import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms"; import { userSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; -import { Spinner } from "@/components/ui/spinner"; +import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; export interface PromptPickerRef { @@ -60,13 +60,21 @@ export const PromptPicker = forwardRef(funct } } + const createPromptIndex = filtered.length; + const totalItems = filtered.length + 1; + const handleSelect = useCallback( (index: number) => { + if (index === createPromptIndex) { + onDone(); + setUserSettingsDialog({ open: true, initialTab: "prompts" }); + return; + } const action = filtered[index]; if (!action) return; onSelect({ name: action.name, prompt: action.prompt, mode: action.mode }); }, - [filtered, onSelect] + [filtered, onSelect, createPromptIndex, onDone, setUserSettingsDialog] ); useEffect(() => { @@ -93,69 +101,104 @@ export const PromptPicker = forwardRef(funct () => ({ selectHighlighted: () => handleSelect(highlightedIndex), moveUp: () => { - if (filtered.length === 0) return; shouldScrollRef.current = true; - setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : filtered.length - 1)); + setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : totalItems - 1)); }, moveDown: () => { - if (filtered.length === 0) return; shouldScrollRef.current = true; - setHighlightedIndex((prev) => (prev < filtered.length - 1 ? prev + 1 : 0)); + setHighlightedIndex((prev) => (prev < totalItems - 1 ? prev + 1 : 0)); }, }), - [filtered.length, highlightedIndex, handleSelect] + [totalItems, highlightedIndex, handleSelect] ); return (
-
+
{isLoading ? ( -
- +
+
+ +
+ {["a", "b", "c", "d", "e"].map((id, i) => ( +
= 3 && "hidden sm:flex" + )} + > + + + + + + +
+ ))}
) : isError ? ( -

Failed to load prompts

+
+

Failed to load prompts

+
) : filtered.length === 0 ? ( -

No matching prompts

+
+

No matching prompts

+
) : ( - filtered.map((action, index) => ( +
+
+ Saved Prompts +
+ {filtered.map((action, index) => ( + + ))} + +
- )) +
)} - -
-
);