84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
export type TaskLinkOptions = {
|
|
layout?: string;
|
|
sessionId?: string;
|
|
searchParams?: URLSearchParams;
|
|
};
|
|
|
|
export function linkToTask(taskId: string, layout?: string): string;
|
|
export function linkToTask(taskId: string, options?: TaskLinkOptions): string;
|
|
export function linkToTask(taskId: string, layoutOrOptions?: string | TaskLinkOptions): string {
|
|
const base = `/t/${encodeURIComponent(taskId)}`;
|
|
const options =
|
|
typeof layoutOrOptions === "string" ? { layout: layoutOrOptions } : (layoutOrOptions ?? {});
|
|
const searchParams = new URLSearchParams(options.searchParams);
|
|
|
|
if (options.layout !== undefined) searchParams.set("layout", options.layout);
|
|
if (options.sessionId !== undefined) searchParams.set("sessionId", options.sessionId);
|
|
|
|
const query = searchParams.toString();
|
|
return query ? `${base}?${query}` : base;
|
|
}
|
|
|
|
export function linkToTaskOverview({
|
|
workspaceId,
|
|
workflowId,
|
|
}: {
|
|
workspaceId?: string;
|
|
workflowId?: string;
|
|
} = {}): string {
|
|
const params = new URLSearchParams({ home: "overview" });
|
|
if (workspaceId) params.set("workspaceId", workspaceId);
|
|
if (workflowId) params.set("workflowId", workflowId);
|
|
return `/?${params.toString()}`;
|
|
}
|
|
|
|
/**
|
|
* The Office home for a workspace. The one builder for this URL: the sidebar
|
|
* brand link, the Home row, and the topbar's home crumb render side by side,
|
|
* so their hrefs must be byte-identical for the same workspace.
|
|
*/
|
|
export function linkToOfficeHome({ workspaceId }: { workspaceId?: string } = {}): string {
|
|
if (!workspaceId) return "/office";
|
|
return `/office?${new URLSearchParams({ workspaceId }).toString()}`;
|
|
}
|
|
|
|
/** Task-detail route prefixes the SPA serves: canonical and compatibility. */
|
|
const TASK_DETAIL_PREFIXES = ["/t/", "/tasks/"];
|
|
|
|
export function normalizePathname(pathname: string): string {
|
|
return pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
}
|
|
|
|
/**
|
|
* True when `pathname` is a task-detail route for `taskId`. Matches both the
|
|
* canonical `/t/:id` and the compatibility `/tasks/:id` paths.
|
|
*/
|
|
export function isTaskDetailPath(pathname: string, taskId: string): boolean {
|
|
const normalized = normalizePathname(pathname);
|
|
return TASK_DETAIL_PREFIXES.some((prefix) => normalized === `${prefix}${taskId}`);
|
|
}
|
|
|
|
/** Replace the browser URL to reflect the active task (no navigation). */
|
|
export function replaceTaskUrl(taskId: string): void {
|
|
if (typeof window === "undefined") return;
|
|
window.history.replaceState({}, "", linkToTask(taskId));
|
|
}
|
|
|
|
export function linkToTasks(workspaceId?: string): string {
|
|
return workspaceId ? `/tasks?workspace=${workspaceId}` : "/tasks";
|
|
}
|
|
|
|
/**
|
|
* The Threads deck: every live agent conversation side by side. `taskId` asks
|
|
* the deck to scroll that task's column into view on arrival, which is how a
|
|
* task page hands a specific discussion back to the deck.
|
|
*/
|
|
export function linkToThreads(workspaceId?: string, taskId?: string, sessionId?: string): string {
|
|
const params = new URLSearchParams();
|
|
if (workspaceId) params.set("workspace", workspaceId);
|
|
if (taskId) params.set("taskId", taskId);
|
|
if (taskId && sessionId) params.set("sessionId", sessionId);
|
|
const query = params.toString();
|
|
return query ? `/threads?${query}` : "/threads";
|
|
}
|