feat(desktop): polish desktop shell and shared WebUI surfaces (#4195)
* feat(desktop): add native host scaffold * feat(webui): track turns and usage in gateway * feat(webui): polish desktop chat experience * feat(apps): add ArcGIS and Joplin logos * feat(desktop): polish shell and shared surfaces * fix(webui): avoid preview chips for glob references * test: align CI expectations for token fallback * feat(webui): preview prompt rail entries * feat(webui): add prompt navigator drawer * style(webui): refine prompt navigator placement * style(webui): align prompt navigator with header actions * style(webui): simplify prompt navigator header * refactor(webui): clean thread resource refresh * feat(desktop): add native reply notifications * fix(webui): preserve desktop restart and replay state * fix(desktop): harden gateway proxy startup * fix(web): fall back when readability is unavailable * fix(desktop): hide window instead of closing on macos * fix(webui): unify desktop header actions * fix(webui): simplify prompt history rows * fix(desktop): log notification delivery failures * chore(desktop): clean source package artifacts * fix(cron): support one-time relative reminders * fix(webui): reveal scroll button in place * Revert "fix(cron): support one-time relative reminders" This reverts commit 4c4661da120a3c7283e0768412bae48604e7390b. * refactor(webui): extract token usage heatmap * docs(desktop): clarify contributor guides --------- Co-authored-by: chengyongru <2755839590@qq.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { fetchSessionAutomations } from "@/lib/api";
|
||||
import type { SessionAutomationJob } from "@/lib/types";
|
||||
|
||||
const AUTOMATIONS_REFRESH_MS = 3000;
|
||||
|
||||
export function useSessionAutomationJobs(open: boolean, token: string, sessionKey: string) {
|
||||
const [jobs, setJobs] = useState<SessionAutomationJob[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadFailed, setLoadFailed] = useState(false);
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
let cancelled = false;
|
||||
let loadedOnce = false;
|
||||
|
||||
const refresh = async (showLoading = false) => {
|
||||
if (showLoading) {
|
||||
setLoading(true);
|
||||
setLoadFailed(false);
|
||||
setJobs([]);
|
||||
}
|
||||
try {
|
||||
const next = await fetchSessionAutomations(token, sessionKey);
|
||||
if (cancelled) return;
|
||||
setJobs(next.jobs);
|
||||
setLoadFailed(false);
|
||||
loadedOnce = true;
|
||||
} catch {
|
||||
if (!cancelled && !loadedOnce) setLoadFailed(true);
|
||||
} finally {
|
||||
if (!cancelled && showLoading) setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
void refresh(true);
|
||||
const refreshId = window.setInterval(() => void refresh(false), AUTOMATIONS_REFRESH_MS);
|
||||
const refreshOnFocus = () => {
|
||||
if (document.visibilityState !== "hidden") void refresh(false);
|
||||
};
|
||||
window.addEventListener("focus", refreshOnFocus);
|
||||
document.addEventListener("visibilitychange", refreshOnFocus);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.clearInterval(refreshId);
|
||||
window.removeEventListener("focus", refreshOnFocus);
|
||||
document.removeEventListener("visibilitychange", refreshOnFocus);
|
||||
};
|
||||
}, [open, sessionKey, token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setNow(Date.now());
|
||||
const tickId = window.setInterval(() => setNow(Date.now()), 1000);
|
||||
return () => window.clearInterval(tickId);
|
||||
}, [open]);
|
||||
|
||||
return { jobs, loading, loadFailed, now };
|
||||
}
|
||||
Reference in New Issue
Block a user