generated from nhcarrigan/template
226 lines
6.3 KiB
TypeScript
226 lines
6.3 KiB
TypeScript
import { get } from "svelte/store";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { claudeStore } from "$lib/stores/claude";
|
|
import { characterState } from "$lib/stores/character";
|
|
import { setSkipNextGreeting } from "$lib/tauri";
|
|
import { searchState } from "$lib/stores/search";
|
|
|
|
export interface SlashCommand {
|
|
name: string;
|
|
description: string;
|
|
usage: string;
|
|
execute: (args: string) => Promise<void> | void;
|
|
}
|
|
|
|
async function changeDirectory(path: string): Promise<void> {
|
|
const conversationId = get(claudeStore.activeConversationId);
|
|
if (!conversationId) {
|
|
claudeStore.addLine("error", "No active conversation");
|
|
return;
|
|
}
|
|
|
|
if (!path.trim()) {
|
|
const currentDir = get(claudeStore.currentWorkingDirectory);
|
|
claudeStore.addLine("system", `Current directory: ${currentDir}`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
characterState.setState("thinking");
|
|
claudeStore.addLine("system", `Changing directory to: ${path}`);
|
|
|
|
const currentDir = get(claudeStore.currentWorkingDirectory);
|
|
const validatedPath = await invoke<string>("validate_directory", { path, currentDir });
|
|
|
|
// Capture conversation history before disconnecting
|
|
const conversationHistory = claudeStore.getConversationHistory();
|
|
|
|
await invoke("stop_claude", { conversationId });
|
|
|
|
// Wait for clean shutdown
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
|
|
claudeStore.setWorkingDirectory(validatedPath);
|
|
|
|
setSkipNextGreeting(true);
|
|
|
|
await invoke("start_claude", {
|
|
conversationId,
|
|
options: {
|
|
working_dir: validatedPath,
|
|
},
|
|
});
|
|
|
|
// Wait for connection to establish
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
|
|
// Restore context if there was conversation history
|
|
if (conversationHistory) {
|
|
const contextMessage = `[CONTEXT RESTORATION]
|
|
I just changed the working directory from ${currentDir} to ${validatedPath}. Here's our conversation so far:
|
|
|
|
${conversationHistory}
|
|
|
|
Please continue where we left off. You are now operating in the new directory.`;
|
|
|
|
await invoke("send_prompt", {
|
|
conversationId,
|
|
message: contextMessage,
|
|
});
|
|
}
|
|
|
|
claudeStore.addLine("system", `Changed directory to: ${validatedPath}`);
|
|
characterState.setState("idle");
|
|
} catch (error) {
|
|
claudeStore.addLine("error", `Failed to change directory: ${error}`);
|
|
characterState.setTemporaryState("error", 3000);
|
|
}
|
|
}
|
|
|
|
async function startNewConversation(): Promise<void> {
|
|
const conversationId = get(claudeStore.activeConversationId);
|
|
if (!conversationId) {
|
|
claudeStore.addLine("error", "No active conversation");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const workingDir = await invoke<string>("get_working_directory", {
|
|
conversationId,
|
|
});
|
|
|
|
claudeStore.addLine("system", "Starting new conversation...");
|
|
characterState.setState("thinking");
|
|
|
|
await invoke("interrupt_claude", { conversationId });
|
|
|
|
claudeStore.clearTerminal();
|
|
|
|
setSkipNextGreeting(true);
|
|
|
|
await invoke("start_claude", {
|
|
conversationId,
|
|
options: {
|
|
working_dir: workingDir,
|
|
},
|
|
});
|
|
|
|
claudeStore.addLine("system", "New conversation started!");
|
|
characterState.setState("idle");
|
|
} catch (error) {
|
|
claudeStore.addLine("error", `Failed to start new conversation: ${error}`);
|
|
characterState.setTemporaryState("error", 3000);
|
|
}
|
|
}
|
|
|
|
export const slashCommands: SlashCommand[] = [
|
|
{
|
|
name: "cd",
|
|
description: "Change the working directory",
|
|
usage: "/cd <path>",
|
|
execute: changeDirectory,
|
|
},
|
|
{
|
|
name: "clear",
|
|
description: "Clear the terminal display (keeps conversation context)",
|
|
usage: "/clear",
|
|
execute: () => {
|
|
claudeStore.clearTerminal();
|
|
claudeStore.addLine("system", "Terminal cleared");
|
|
},
|
|
},
|
|
{
|
|
name: "new",
|
|
description: "Start a fresh conversation (resets context)",
|
|
usage: "/new",
|
|
execute: startNewConversation,
|
|
},
|
|
{
|
|
name: "help",
|
|
description: "Show available slash commands",
|
|
usage: "/help",
|
|
execute: () => {
|
|
const helpText = slashCommands
|
|
.map((cmd) => ` ${cmd.usage.padEnd(12)} - ${cmd.description}`)
|
|
.join("\n");
|
|
claudeStore.addLine("system", `Available commands:\n${helpText}`);
|
|
},
|
|
},
|
|
{
|
|
name: "search",
|
|
description: "Search within the conversation (use /search to clear)",
|
|
usage: "/search [query]",
|
|
execute: (args: string) => {
|
|
if (!args.trim()) {
|
|
searchState.clear();
|
|
claudeStore.addLine("system", "Search cleared");
|
|
return;
|
|
}
|
|
searchState.setQuery(args.trim());
|
|
claudeStore.addLine("system", `Searching for: "${args.trim()}"`);
|
|
},
|
|
},
|
|
{
|
|
name: "summarise",
|
|
description: "Get a summary of the entire conversation",
|
|
usage: "/summarise",
|
|
execute: async () => {
|
|
const conversationId = get(claudeStore.activeConversationId);
|
|
if (!conversationId) {
|
|
claudeStore.addLine("error", "No active conversation");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
claudeStore.addLine("system", "Requesting conversation summary...");
|
|
await invoke("send_prompt", {
|
|
conversationId,
|
|
message:
|
|
"Please provide a comprehensive summary of our entire conversation so far, including the key topics we've discussed, decisions made, and any important context.",
|
|
});
|
|
} catch (error) {
|
|
claudeStore.addLine("error", `Failed to request summary: ${error}`);
|
|
}
|
|
},
|
|
},
|
|
];
|
|
|
|
export function parseSlashCommand(input: string): {
|
|
command: SlashCommand | null;
|
|
args: string;
|
|
} {
|
|
const trimmed = input.trim();
|
|
|
|
if (!trimmed.startsWith("/")) {
|
|
return { command: null, args: "" };
|
|
}
|
|
|
|
const parts = trimmed.slice(1).split(/\s+/);
|
|
const commandName = parts[0]?.toLowerCase();
|
|
const args = parts.slice(1).join(" ");
|
|
|
|
const command = slashCommands.find((cmd) => cmd.name.toLowerCase() === commandName);
|
|
|
|
return { command: command || null, args };
|
|
}
|
|
|
|
export function getMatchingCommands(input: string): SlashCommand[] {
|
|
const trimmed = input.trim();
|
|
|
|
if (!trimmed.startsWith("/")) {
|
|
return [];
|
|
}
|
|
|
|
const partial = trimmed.slice(1).toLowerCase();
|
|
|
|
if (partial === "") {
|
|
return slashCommands;
|
|
}
|
|
|
|
return slashCommands.filter((cmd) => cmd.name.toLowerCase().startsWith(partial));
|
|
}
|
|
|
|
export function isSlashCommand(input: string): boolean {
|
|
return input.trim().startsWith("/");
|
|
}
|