mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-29 02:46:25 +02:00
Merge commit 'e1e4bb4706' into dev_mod
This commit is contained in:
commit
5d3142332b
85 changed files with 2357 additions and 3132 deletions
|
|
@ -29,8 +29,6 @@ interface DocumentMentionPickerProps {
|
|||
onDone: () => void;
|
||||
initialSelectedDocuments?: Pick<Document, "id" | "title" | "document_type">[];
|
||||
externalSearch?: string;
|
||||
/** Positioning styles for the container */
|
||||
containerStyle?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
|
@ -75,7 +73,6 @@ export const DocumentMentionPicker = forwardRef<
|
|||
onDone,
|
||||
initialSelectedDocuments = [],
|
||||
externalSearch = "",
|
||||
containerStyle,
|
||||
},
|
||||
ref
|
||||
) {
|
||||
|
|
@ -394,19 +391,9 @@ 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 (
|
||||
<div
|
||||
className="fixed shadow-2xl rounded-lg border border-border dark:border-white/5 overflow-hidden bg-popover dark:bg-neutral-900 flex flex-col w-[280px] sm:w-[320px] select-none"
|
||||
style={{
|
||||
zIndex: 9999,
|
||||
...containerStyle,
|
||||
}}
|
||||
className="shadow-2xl rounded-lg border border-border dark:border-white/5 overflow-hidden bg-popover dark:bg-neutral-900 flex flex-col w-[280px] sm:w-[320px] select-none"
|
||||
onKeyDown={handleKeyDown}
|
||||
role="listbox"
|
||||
tabIndex={-1}
|
||||
|
|
@ -547,7 +534,11 @@ export const DocumentMentionPicker = forwardRef<
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="py-1 px-2">
|
||||
<p className="px-3 py-2 text-xs text-muted-foreground">No matching documents</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -28,11 +28,10 @@ interface PromptPickerProps {
|
|||
onSelect: (action: { name: string; prompt: string; mode: "transform" | "explore" }) => void;
|
||||
onDone: () => void;
|
||||
externalSearch?: string;
|
||||
containerStyle?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(function PromptPicker(
|
||||
{ onSelect, onDone, externalSearch = "", containerStyle },
|
||||
{ onSelect, onDone, externalSearch = "" },
|
||||
ref
|
||||
) {
|
||||
const setUserSettingsDialog = useSetAtom(userSettingsDialogAtom);
|
||||
|
|
@ -60,13 +59,21 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(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 +100,98 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(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 (
|
||||
<div
|
||||
className="w-64 rounded-lg border bg-popover shadow-lg overflow-hidden"
|
||||
style={containerStyle}
|
||||
>
|
||||
<div ref={scrollContainerRef} className="max-h-48 overflow-y-auto py-1">
|
||||
<div className="shadow-2xl rounded-lg border border-border dark:border-white/5 overflow-hidden bg-popover dark:bg-neutral-900 flex flex-col w-[280px] sm:w-[320px] select-none">
|
||||
<div ref={scrollContainerRef} className="max-h-[180px] sm:max-h-[280px] overflow-y-auto">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-3">
|
||||
<Spinner className="size-4" />
|
||||
<div className="py-1 px-2">
|
||||
<div className="px-3 py-2">
|
||||
<Skeleton className="h-[16px] w-24" />
|
||||
</div>
|
||||
{["a", "b", "c", "d", "e"].map((id, i) => (
|
||||
<div
|
||||
key={id}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-2 px-3 py-2 text-left rounded-md",
|
||||
i >= 3 && "hidden sm:flex"
|
||||
)}
|
||||
>
|
||||
<span className="shrink-0">
|
||||
<Skeleton className="h-4 w-4" />
|
||||
</span>
|
||||
<span className="flex-1 text-sm">
|
||||
<Skeleton className="h-[20px]" style={{ width: `${60 + ((i * 7) % 30)}%` }} />
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : isError ? (
|
||||
<p className="px-3 py-2 text-xs text-destructive">Failed to load prompts</p>
|
||||
<div className="py-1 px-2">
|
||||
<p className="px-3 py-2 text-xs text-destructive">Failed to load prompts</p>
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<p className="px-3 py-2 text-xs text-muted-foreground">No matching prompts</p>
|
||||
<div className="py-1 px-2">
|
||||
<p className="px-3 py-2 text-xs text-muted-foreground">No matching prompts</p>
|
||||
</div>
|
||||
) : (
|
||||
filtered.map((action, index) => (
|
||||
<div className="py-1 px-2">
|
||||
<div className="px-3 py-2 text-xs font-bold text-muted-foreground/55">
|
||||
Saved Prompts
|
||||
</div>
|
||||
{filtered.map((action, index) => (
|
||||
<button
|
||||
key={action.id}
|
||||
ref={(el) => {
|
||||
if (el) itemRefs.current.set(index, el);
|
||||
else itemRefs.current.delete(index);
|
||||
}}
|
||||
type="button"
|
||||
onClick={() => handleSelect(index)}
|
||||
onMouseEnter={() => setHighlightedIndex(index)}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-2 px-3 py-2 text-left text-sm transition-colors rounded-md cursor-pointer",
|
||||
index === highlightedIndex && "bg-accent"
|
||||
)}
|
||||
>
|
||||
<span className="shrink-0 text-muted-foreground">
|
||||
<Zap className="size-4" />
|
||||
</span>
|
||||
<span className="flex-1 text-sm truncate">{action.name}</span>
|
||||
</button>
|
||||
))}
|
||||
|
||||
<div className="mx-2 my-1 border-t border-border dark:border-white/5" />
|
||||
<button
|
||||
key={action.id}
|
||||
ref={(el) => {
|
||||
if (el) itemRefs.current.set(index, el);
|
||||
else itemRefs.current.delete(index);
|
||||
if (el) itemRefs.current.set(createPromptIndex, el);
|
||||
else itemRefs.current.delete(createPromptIndex);
|
||||
}}
|
||||
type="button"
|
||||
onClick={() => handleSelect(index)}
|
||||
onMouseEnter={() => setHighlightedIndex(index)}
|
||||
onClick={() => handleSelect(createPromptIndex)}
|
||||
onMouseEnter={() => setHighlightedIndex(createPromptIndex)}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 px-3 py-1.5 text-sm cursor-pointer",
|
||||
index === highlightedIndex ? "bg-accent" : "hover:bg-accent/50"
|
||||
"w-full flex items-center gap-2 px-3 py-2 text-left text-sm transition-colors rounded-md cursor-pointer text-muted-foreground",
|
||||
highlightedIndex === createPromptIndex ? "bg-accent text-foreground" : "hover:text-foreground hover:bg-accent/50"
|
||||
)}
|
||||
>
|
||||
<span className="text-muted-foreground">
|
||||
<Zap className="size-3.5" />
|
||||
<span className="shrink-0">
|
||||
<Plus className="size-4" />
|
||||
</span>
|
||||
<span className="truncate">{action.name}</span>
|
||||
<span>Create prompt</span>
|
||||
</button>
|
||||
))
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="my-1 h-px bg-border mx-2" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onDone();
|
||||
setUserSettingsDialog({ open: true, initialTab: "prompts" });
|
||||
}}
|
||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:text-foreground hover:bg-accent/50 cursor-pointer"
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
<span>Create prompt</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue