redesign action menu: grid layout, search, Ask SurfSense, fix action groups

This commit is contained in:
CREDO23 2026-03-27 19:47:02 +02:00
parent 151d6a853e
commit f36e5f8287
3 changed files with 120 additions and 83 deletions

View file

@ -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) {
setTimeout(() => { handledRef.current = true;
aui.composer().setText(prompt); setTimeout(() => {
aui.composer().send(); aui.composer().setText(prompt);
}, 500); aui.composer().send();
}, 500);
} else if (initialText) {
handledRef.current = true;
setTimeout(() => {
aui.composer().setText(initialText);
}, 500);
}
}, [searchParams, aui]); }, [searchParams, aui]);
return null; return null;

View file

@ -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",
}, },
]; ];

View file

@ -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,80 +41,109 @@ 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">
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground">Transform</div>
{transformActions.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" /> {filteredExplore.length > 0 && (
<>
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground">Explore</div> <div className="mb-2 text-xs font-medium text-muted-foreground">Explore</div>
{exploreActions.map((action) => ( <div className="mb-3 grid grid-cols-2 gap-1.5">
<button {filteredExplore.map((action) => (
key={action.id} <button
type="button" key={action.id}
onClick={() => handleAction(action.id)} type="button"
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded-sm cursor-pointer" 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"
{ICONS[action.icon]} >
{action.name} <span className="text-muted-foreground">{ICONS[action.icon]}</span>
</button> {action.name}
))} </button>
))}
<div className="my-1 h-px bg-border" /> </div>
</>
<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" />
<button
type="button"
onClick={() => navigateToChat(clipboardText, "explore")}
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent rounded-sm cursor-pointer"
>
<MessageSquare className="size-4" />
Ask anything...
</button>
</div>
)} )}
<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">
Custom actions coming soon
</div>
</div>
<div className="border-t px-3 py-2">
<button
type="button"
onClick={navigateWithInitialText}
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" />
Ask SurfSense...
</button>
</div> </div>
</div> </div>
); );