switch to on-demand permission requests and improve suggestion UX

This commit is contained in:
CREDO23 2026-04-03 19:57:48 +02:00
parent aeb3f13f91
commit c5aa869adb
12 changed files with 195 additions and 89 deletions

View file

@ -11,12 +11,40 @@ type SSEEvent =
| { type: "finish" }
| { type: "error"; errorText: string };
function friendlyError(raw: string | number): string {
if (typeof raw === "number") {
if (raw === 401) return "Please sign in to use suggestions.";
if (raw === 403) return "You don\u2019t have permission for this.";
if (raw === 404) return "Suggestion service not found. Is the backend running?";
if (raw >= 500) return "Something went wrong on the server. Try again.";
return "Something went wrong. Try again.";
}
const lower = raw.toLowerCase();
if (lower.includes("not authenticated") || lower.includes("unauthorized"))
return "Please sign in to use suggestions.";
if (lower.includes("no vision llm configured") || lower.includes("no llm configured"))
return "No Vision LLM configured. Set one in search space settings.";
if (lower.includes("fetch") || lower.includes("network") || lower.includes("econnrefused"))
return "Can\u2019t reach the server. Check your connection.";
return "Something went wrong. Try again.";
}
const AUTO_DISMISS_MS = 3000;
export default function SuggestionPage() {
const [suggestion, setSuggestion] = useState("");
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const abortRef = useRef<AbortController | null>(null);
useEffect(() => {
if (!error) return;
const timer = setTimeout(() => {
window.electronAPI?.dismissSuggestion?.();
}, AUTO_DISMISS_MS);
return () => clearTimeout(timer);
}, [error]);
const fetchSuggestion = useCallback(
async (screenshot: string, searchSpaceId: string) => {
abortRef.current?.abort();
@ -29,7 +57,7 @@ export default function SuggestionPage() {
const token = getBearerToken();
if (!token) {
setError("Not authenticated");
setError(friendlyError("not authenticated"));
setIsLoading(false);
return;
}
@ -55,13 +83,13 @@ export default function SuggestionPage() {
);
if (!response.ok) {
setError(`Error: ${response.status}`);
setError(friendlyError(response.status));
setIsLoading(false);
return;
}
if (!response.body) {
setError("No response body");
setError(friendlyError("network error"));
setIsLoading(false);
return;
}
@ -94,7 +122,7 @@ export default function SuggestionPage() {
return updated;
});
} else if (parsed.type === "error") {
setError(parsed.errorText);
setError(friendlyError(parsed.errorText));
}
} catch {
continue;
@ -104,7 +132,7 @@ export default function SuggestionPage() {
}
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") return;
setError("Failed to get suggestion");
setError(friendlyError("network error"));
} finally {
setIsLoading(false);
}
@ -145,15 +173,28 @@ export default function SuggestionPage() {
);
}
const handleAccept = () => {
if (suggestion) {
window.electronAPI?.acceptSuggestion?.(suggestion);
}
};
const handleDismiss = () => {
window.electronAPI?.dismissSuggestion?.();
};
if (!suggestion) return null;
return (
<div className="suggestion-tooltip">
<p className="suggestion-text">{suggestion}</p>
<div className="suggestion-hint">
<kbd>Tab</kbd> accept
<span className="suggestion-separator" />
<kbd>Esc</kbd> dismiss
<div className="suggestion-actions">
<button className="suggestion-btn suggestion-btn-accept" onClick={handleAccept}>
Accept
</button>
<button className="suggestion-btn suggestion-btn-dismiss" onClick={handleDismiss}>
Dismiss
</button>
</div>
</div>
);

View file

@ -36,32 +36,44 @@ html, body {
white-space: pre-wrap;
}
.suggestion-hint {
color: #666;
font-size: 11px;
.suggestion-actions {
display: flex;
align-items: center;
gap: 6px;
justify-content: flex-end;
gap: 4px;
border-top: 1px solid #2a2a2a;
padding-top: 6px;
}
.suggestion-hint kbd {
background: #2a2a2a;
border: 1px solid #3c3c3c;
.suggestion-btn {
padding: 2px 8px;
border-radius: 3px;
padding: 0 4px;
border: 1px solid #3c3c3c;
font-family: inherit;
font-size: 10px;
font-weight: 600;
color: #999;
line-height: 18px;
font-weight: 500;
cursor: pointer;
line-height: 16px;
transition: background 0.15s, border-color 0.15s;
}
.suggestion-separator {
width: 1px;
height: 10px;
.suggestion-btn-accept {
background: #2563eb;
border-color: #3b82f6;
color: #fff;
}
.suggestion-btn-accept:hover {
background: #1d4ed8;
}
.suggestion-btn-dismiss {
background: #2a2a2a;
color: #999;
}
.suggestion-btn-dismiss:hover {
background: #333;
color: #ccc;
}
.suggestion-error {