mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-28 18:36:23 +02:00
feat: handle multi-option suggestions in suggestion page UI
This commit is contained in:
parent
55845d68ef
commit
2602248e7a
1 changed files with 50 additions and 18 deletions
|
|
@ -14,6 +14,10 @@ type SSEEvent =
|
||||||
| {
|
| {
|
||||||
type: "data-thinking-step";
|
type: "data-thinking-step";
|
||||||
data: { id: string; title: string; status: string; items: string[] };
|
data: { id: string; title: string; status: string; items: string[] };
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "data-suggestions";
|
||||||
|
data: { options: string[] };
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AgentStep {
|
interface AgentStep {
|
||||||
|
|
@ -70,10 +74,11 @@ function StepIcon({ status }: { status: string }) {
|
||||||
|
|
||||||
export default function SuggestionPage() {
|
export default function SuggestionPage() {
|
||||||
const api = useElectronAPI();
|
const api = useElectronAPI();
|
||||||
const [suggestion, setSuggestion] = useState("");
|
const [options, setOptions] = useState<string[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [steps, setSteps] = useState<AgentStep[]>([]);
|
const [steps, setSteps] = useState<AgentStep[]>([]);
|
||||||
|
const [expandedOption, setExpandedOption] = useState<number | null>(null);
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
const isDesktop = !!api?.onAutocompleteContext;
|
const isDesktop = !!api?.onAutocompleteContext;
|
||||||
|
|
@ -99,9 +104,10 @@ export default function SuggestionPage() {
|
||||||
abortRef.current = controller;
|
abortRef.current = controller;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setSuggestion("");
|
setOptions([]);
|
||||||
setError(null);
|
setError(null);
|
||||||
setSteps([]);
|
setSteps([]);
|
||||||
|
setExpandedOption(null);
|
||||||
|
|
||||||
let token = getBearerToken();
|
let token = getBearerToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
|
@ -165,8 +171,8 @@ export default function SuggestionPage() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed: SSEEvent = JSON.parse(data);
|
const parsed: SSEEvent = JSON.parse(data);
|
||||||
if (parsed.type === "text-delta") {
|
if (parsed.type === "data-suggestions") {
|
||||||
setSuggestion((prev) => prev + parsed.delta);
|
setOptions(parsed.data.options);
|
||||||
} else if (parsed.type === "error") {
|
} else if (parsed.type === "error") {
|
||||||
setError(friendlyError(parsed.errorText));
|
setError(friendlyError(parsed.errorText));
|
||||||
} else if (parsed.type === "data-thinking-step") {
|
} else if (parsed.type === "data-thinking-step") {
|
||||||
|
|
@ -226,7 +232,7 @@ export default function SuggestionPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const showLoading = isLoading && !suggestion;
|
const showLoading = isLoading && options.length === 0;
|
||||||
|
|
||||||
if (showLoading) {
|
if (showLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -258,29 +264,55 @@ export default function SuggestionPage() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAccept = () => {
|
const handleSelect = (text: string) => {
|
||||||
if (suggestion) {
|
api?.acceptSuggestion?.(text);
|
||||||
api?.acceptSuggestion?.(suggestion);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDismiss = () => {
|
const handleDismiss = () => {
|
||||||
api?.dismissSuggestion?.();
|
api?.dismissSuggestion?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!suggestion) return null;
|
const TRUNCATE_LENGTH = 120;
|
||||||
|
|
||||||
|
if (options.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="suggestion-tooltip">
|
<div className="suggestion-tooltip">
|
||||||
<p className="suggestion-text">{suggestion}</p>
|
<div className="suggestion-options">
|
||||||
|
{options.map((option, index) => {
|
||||||
|
const isExpanded = expandedOption === index;
|
||||||
|
const needsTruncation = option.length > TRUNCATE_LENGTH;
|
||||||
|
const displayText =
|
||||||
|
needsTruncation && !isExpanded
|
||||||
|
? option.slice(0, TRUNCATE_LENGTH) + "…"
|
||||||
|
: option;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
type="button"
|
||||||
|
className="suggestion-option"
|
||||||
|
onClick={() => handleSelect(option)}
|
||||||
|
>
|
||||||
|
<span className="option-number">{index + 1}</span>
|
||||||
|
<span className="option-text">{displayText}</span>
|
||||||
|
{needsTruncation && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="option-expand"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setExpandedOption(isExpanded ? null : index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isExpanded ? "less" : "more"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
<div className="suggestion-actions">
|
<div className="suggestion-actions">
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="suggestion-btn suggestion-btn-accept"
|
|
||||||
onClick={handleAccept}
|
|
||||||
>
|
|
||||||
Accept
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="suggestion-btn suggestion-btn-dismiss"
|
className="suggestion-btn suggestion-btn-dismiss"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue