/ extensions / anycopy / enter-navigation.ts
enter-navigation.ts
  1  export type AnycopyEnterNavigationResult = "reopen" | "closed";
  2  
  3  export type AnycopySummaryChoice = "No summary" | "Summarize" | "Summarize with custom prompt";
  4  
  5  export type AnycopyEnterNavigationDeps = {
  6  	entryId: string;
  7  	currentLeafIdForNoop: string | null;
  8  	skipSummaryPrompt: boolean;
  9  	close: () => void;
 10  	reopen: (options: { initialSelectedId: string }) => void;
 11  	navigateTree: (
 12  		targetId: string,
 13  		options?: { summarize?: boolean; customInstructions?: string },
 14  	) => Promise<{ cancelled: boolean; aborted?: boolean }>;
 15  	ui: {
 16  		select: (title: string, options: AnycopySummaryChoice[]) => Promise<AnycopySummaryChoice | undefined>;
 17  		editor: (title: string) => Promise<string | undefined>;
 18  		setStatus: (source: string, message: string) => void;
 19  		setWorkingMessage: (message?: string) => void;
 20  		notify: (message: string, level: "error") => void;
 21  	};
 22  };
 23  
 24  export function createAnycopyEnterNavigationLauncher(
 25  	run: (entryId: string) => Promise<AnycopyEnterNavigationResult>,
 26  ): (entryId: string) => void {
 27  	let navigationInFlight = false;
 28  
 29  	return (entryId: string) => {
 30  		if (navigationInFlight) {
 31  			return;
 32  		}
 33  
 34  		navigationInFlight = true;
 35  		void run(entryId).finally(() => {
 36  			navigationInFlight = false;
 37  		});
 38  	};
 39  }
 40  
 41  export async function runAnycopyEnterNavigation(
 42  	deps: AnycopyEnterNavigationDeps,
 43  ): Promise<AnycopyEnterNavigationResult> {
 44  	const { entryId, currentLeafIdForNoop, skipSummaryPrompt, close, reopen, navigateTree, ui } = deps;
 45  
 46  	if (currentLeafIdForNoop !== null && entryId === currentLeafIdForNoop) {
 47  		close();
 48  		ui.setStatus("anycopy", "Already at this point");
 49  		return "closed";
 50  	}
 51  
 52  	close();
 53  
 54  	let wantsSummary = false;
 55  	let customInstructions: string | undefined;
 56  
 57  	if (!skipSummaryPrompt) {
 58  		while (true) {
 59  			const choice = await ui.select("Summarize branch?", [
 60  				"No summary",
 61  				"Summarize",
 62  				"Summarize with custom prompt",
 63  			]);
 64  
 65  			if (choice === undefined) {
 66  				reopen({ initialSelectedId: entryId });
 67  				return "reopen";
 68  			}
 69  
 70  			if (choice === "No summary") {
 71  				wantsSummary = false;
 72  				customInstructions = undefined;
 73  				break;
 74  			}
 75  
 76  			if (choice === "Summarize") {
 77  				wantsSummary = true;
 78  				customInstructions = undefined;
 79  				break;
 80  			}
 81  
 82  			customInstructions = await ui.editor("Custom summarization instructions");
 83  			if (customInstructions === undefined) {
 84  				continue;
 85  			}
 86  
 87  			wantsSummary = true;
 88  			break;
 89  		}
 90  	}
 91  
 92  	ui.setWorkingMessage("Navigating tree…");
 93  
 94  	try {
 95  		const result = await navigateTree(entryId, {
 96  			summarize: wantsSummary,
 97  			customInstructions,
 98  		});
 99  
100  		if (result.cancelled) {
101  			if (wantsSummary) {
102  				ui.setStatus("anycopy", "Branch summarization cancelled");
103  				reopen({ initialSelectedId: entryId });
104  				return "reopen";
105  			}
106  
107  			ui.setStatus("anycopy", "Navigation cancelled");
108  			return "closed";
109  		}
110  
111  		return "closed";
112  	} catch (error) {
113  		ui.notify(error instanceof Error ? error.message : String(error), "error");
114  		return "closed";
115  	} finally {
116  		ui.setWorkingMessage();
117  	}
118  }