- Up to three chats side by side (#/s/a+b+c), "Open beside" in the sidebar menu
- File manager tabs of a chat stored per user on the server (/sessions/{id}/tabs)
- Personal modes for every user (Settings → My modes, /account/modes)
- Budget meters (total, provider, images, opencode Go windows) with push warnings
for admins at the warning share and 100 %, shown in the status overlay
- scripts/move-server.sh: move to another Docker host with bind mounts (Unraid);
scripts/smoke-test.sh
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
38 lines
1.1 KiB
TypeScript
38 lines
1.1 KiB
TypeScript
import { useSyncExternalStore } from "react";
|
|
|
|
/** Hash routes: #/ , #/s/<id>, #/new, #/settings[/tab], #/stats */
|
|
export function useRoute(): string[] {
|
|
const hash = useSyncExternalStore(
|
|
(l) => {
|
|
window.addEventListener("hashchange", l);
|
|
return () => window.removeEventListener("hashchange", l);
|
|
},
|
|
() => location.hash,
|
|
);
|
|
return hash.replace(/^#\/?/, "").split("/").filter(Boolean);
|
|
}
|
|
|
|
export function navigate(path: string) {
|
|
location.hash = path.startsWith("/") ? path : `/${path}`;
|
|
}
|
|
|
|
/** Chats shown side by side: #/s/<id>+<id>+<id> (at most 3). */
|
|
export const MAX_PANES = 3;
|
|
|
|
export function openChats(): string[] {
|
|
const m = location.hash.match(/^#\/s\/([^/]+)/);
|
|
return m ? m[1].split("+").filter(Boolean) : [];
|
|
}
|
|
|
|
/** Add a chat as another column (replaces the last one when all columns are used). */
|
|
export function openBeside(id: string) {
|
|
const ids = openChats().filter((x) => x !== id);
|
|
if (ids.length >= MAX_PANES) ids.pop();
|
|
navigate(`/s/${[...ids, id].join("+")}`);
|
|
}
|
|
|
|
export function closePane(id: string) {
|
|
const ids = openChats().filter((x) => x !== id);
|
|
navigate(ids.length ? `/s/${ids.join("+")}` : "/");
|
|
}
|