mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-06 20:15:17 +02:00
redesign action menu: grid layout, search, Ask SurfSense, fix action groups
This commit is contained in:
parent
151d6a853e
commit
f36e5f8287
3 changed files with 120 additions and 83 deletions
|
|
@ -162,19 +162,26 @@ const TOOLS_WITH_UI = new Set([
|
||||||
function QuickAskAutoSubmit() {
|
function QuickAskAutoSubmit() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const aui = useAui();
|
const aui = useAui();
|
||||||
const submittedRef = useRef(false);
|
const handledRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!window.electronAPI || submittedRef.current) return;
|
if (!window.electronAPI || handledRef.current) return;
|
||||||
|
|
||||||
const prompt = searchParams.get("quickAskPrompt");
|
const prompt = searchParams.get("quickAskPrompt");
|
||||||
if (!prompt) return;
|
const initialText = searchParams.get("quickAskInitialText");
|
||||||
|
|
||||||
submittedRef.current = true;
|
if (prompt) {
|
||||||
|
handledRef.current = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
aui.composer().setText(prompt);
|
aui.composer().setText(prompt);
|
||||||
aui.composer().send();
|
aui.composer().send();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
} else if (initialText) {
|
||||||
|
handledRef.current = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
aui.composer().setText(initialText);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}, [searchParams, aui]);
|
}, [searchParams, aui]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,14 @@ export const DEFAULT_ACTIONS: QuickAskAction[] = [
|
||||||
icon: "pen-line",
|
icon: "pen-line",
|
||||||
group: "transform",
|
group: "transform",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "summarize",
|
||||||
|
name: "Summarize",
|
||||||
|
prompt: "Summarize the following text concisely. Return only the summary, nothing else.\n\n{selection}",
|
||||||
|
mode: "transform",
|
||||||
|
icon: "list",
|
||||||
|
group: "transform",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "explain",
|
id: "explain",
|
||||||
name: "Explain",
|
name: "Explain",
|
||||||
|
|
@ -42,27 +50,19 @@ export const DEFAULT_ACTIONS: QuickAskAction[] = [
|
||||||
group: "explore",
|
group: "explore",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "summarize",
|
id: "ask-knowledge-base",
|
||||||
name: "Summarize",
|
name: "Ask my knowledge base",
|
||||||
prompt: "Summarize the following text:\n\n{selection}",
|
|
||||||
mode: "explore",
|
|
||||||
icon: "list",
|
|
||||||
group: "explore",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "search-knowledge",
|
|
||||||
name: "Search my knowledge",
|
|
||||||
prompt: "Search my knowledge base for information related to:\n\n{selection}",
|
prompt: "Search my knowledge base for information related to:\n\n{selection}",
|
||||||
mode: "explore",
|
mode: "explore",
|
||||||
icon: "search",
|
icon: "search",
|
||||||
group: "knowledge",
|
group: "explore",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "search-web",
|
id: "look-up-web",
|
||||||
name: "Search the web",
|
name: "Look up on the web",
|
||||||
prompt: "Search the web for information about:\n\n{selection}",
|
prompt: "Search the web for information about:\n\n{selection}",
|
||||||
mode: "explore",
|
mode: "explore",
|
||||||
icon: "globe",
|
icon: "globe",
|
||||||
group: "knowledge",
|
group: "explore",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
PenLine,
|
PenLine,
|
||||||
Search,
|
Search,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { DEFAULT_ACTIONS } from "./actions";
|
import { DEFAULT_ACTIONS } from "./actions";
|
||||||
|
|
||||||
const ICONS: Record<string, React.ReactNode> = {
|
const ICONS: Record<string, React.ReactNode> = {
|
||||||
|
|
@ -27,6 +27,7 @@ const ICONS: Record<string, React.ReactNode> = {
|
||||||
|
|
||||||
export default function QuickAskPage() {
|
export default function QuickAskPage() {
|
||||||
const [clipboardText, setClipboardText] = useState("");
|
const [clipboardText, setClipboardText] = useState("");
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.electronAPI?.getQuickAskText().then((text) => {
|
window.electronAPI?.getQuickAskText().then((text) => {
|
||||||
|
|
@ -40,81 +41,110 @@ export default function QuickAskPage() {
|
||||||
window.location.href = `/dashboard?quickAskPrompt=${encoded}`;
|
window.location.href = `/dashboard?quickAskPrompt=${encoded}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const navigateWithInitialText = () => {
|
||||||
|
if (!clipboardText) return;
|
||||||
|
sessionStorage.setItem("quickAskMode", "explore");
|
||||||
|
const encoded = encodeURIComponent(clipboardText);
|
||||||
|
window.location.href = `/dashboard?quickAskInitialText=${encoded}`;
|
||||||
|
};
|
||||||
|
|
||||||
const handleAction = (actionId: string) => {
|
const handleAction = (actionId: string) => {
|
||||||
const action = DEFAULT_ACTIONS.find((a) => a.id === actionId);
|
const action = DEFAULT_ACTIONS.find((a) => a.id === actionId);
|
||||||
if (!action) return;
|
if (!action || !clipboardText) return;
|
||||||
const prompt = action.prompt.replace("{selection}", clipboardText);
|
const prompt = action.prompt.replace("{selection}", clipboardText);
|
||||||
navigateToChat(prompt, action.mode);
|
navigateToChat(prompt, action.mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformActions = DEFAULT_ACTIONS.filter((a) => a.group === "transform");
|
const transformActions = DEFAULT_ACTIONS.filter((a) => a.group === "transform");
|
||||||
const exploreActions = DEFAULT_ACTIONS.filter((a) => a.group === "explore");
|
const exploreActions = DEFAULT_ACTIONS.filter((a) => a.group === "explore");
|
||||||
const knowledgeActions = DEFAULT_ACTIONS.filter((a) => a.group === "knowledge");
|
|
||||||
|
const filteredTransform = useMemo(
|
||||||
|
() => transformActions.filter((a) => a.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||||
|
[searchQuery]
|
||||||
|
);
|
||||||
|
const filteredExplore = useMemo(
|
||||||
|
() => exploreActions.filter((a) => a.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||||
|
[searchQuery]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!clipboardText) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen items-center justify-center bg-background">
|
||||||
|
<div className="text-sm text-muted-foreground">Loading...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col bg-background">
|
<div className="flex h-screen flex-col bg-background">
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="border-b px-3 py-2">
|
||||||
{!clipboardText && (
|
<div className="flex items-center gap-2 rounded-md border bg-muted/50 px-3 py-1.5">
|
||||||
<div className="p-4 text-center text-sm text-muted-foreground">Loading...</div>
|
<Search className="size-3.5 text-muted-foreground" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search actions..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto px-3 py-2">
|
||||||
|
{filteredTransform.length > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-2 text-xs font-medium text-muted-foreground">Transform</div>
|
||||||
|
<div className="mb-3 grid grid-cols-2 gap-1.5">
|
||||||
|
{filteredTransform.map((action) => (
|
||||||
|
<button
|
||||||
|
key={action.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleAction(action.id)}
|
||||||
|
className="flex items-center gap-2 rounded-md border px-3 py-2.5 text-sm transition-colors hover:bg-accent hover:border-accent-foreground/20 cursor-pointer"
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground">{ICONS[action.icon]}</span>
|
||||||
|
{action.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{clipboardText && (
|
|
||||||
<div className="py-1">
|
{filteredExplore.length > 0 && (
|
||||||
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground">Transform</div>
|
<>
|
||||||
{transformActions.map((action) => (
|
<div className="mb-2 text-xs font-medium text-muted-foreground">Explore</div>
|
||||||
|
<div className="mb-3 grid grid-cols-2 gap-1.5">
|
||||||
|
{filteredExplore.map((action) => (
|
||||||
<button
|
<button
|
||||||
key={action.id}
|
key={action.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleAction(action.id)}
|
onClick={() => handleAction(action.id)}
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded-sm cursor-pointer"
|
className="flex items-center gap-2 rounded-md border px-3 py-2.5 text-sm transition-colors hover:bg-accent hover:border-accent-foreground/20 cursor-pointer"
|
||||||
>
|
>
|
||||||
{ICONS[action.icon]}
|
<span className="text-muted-foreground">{ICONS[action.icon]}</span>
|
||||||
{action.name}
|
{action.name}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="my-1 h-px bg-border" />
|
<div className="mb-2 text-xs font-medium text-muted-foreground">My Actions</div>
|
||||||
|
<div className="mb-3 rounded-md border border-dashed px-3 py-4 text-center text-xs text-muted-foreground">
|
||||||
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground">Explore</div>
|
Custom actions coming soon
|
||||||
{exploreActions.map((action) => (
|
</div>
|
||||||
<button
|
</div>
|
||||||
key={action.id}
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleAction(action.id)}
|
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded-sm cursor-pointer"
|
|
||||||
>
|
|
||||||
{ICONS[action.icon]}
|
|
||||||
{action.name}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className="my-1 h-px bg-border" />
|
|
||||||
|
|
||||||
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground">Knowledge</div>
|
|
||||||
{knowledgeActions.map((action) => (
|
|
||||||
<button
|
|
||||||
key={action.id}
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleAction(action.id)}
|
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded-sm cursor-pointer"
|
|
||||||
>
|
|
||||||
{ICONS[action.icon]}
|
|
||||||
{action.name}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className="my-1 h-px bg-border" />
|
|
||||||
|
|
||||||
|
<div className="border-t px-3 py-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigateToChat(clipboardText, "explore")}
|
onClick={navigateWithInitialText}
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded-sm cursor-pointer"
|
className="flex w-full items-center justify-center gap-2 rounded-md bg-primary px-3 py-2.5 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 cursor-pointer"
|
||||||
>
|
>
|
||||||
<MessageSquare className="size-4" />
|
<MessageSquare className="size-4" />
|
||||||
Ask anything...
|
Ask SurfSense...
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue