Files
storkit/frontend/src/App.tsx

178 lines
4.6 KiB
TypeScript
Raw Normal View History

import * as React from "react";
import { api } from "./api/client";
import { Chat } from "./components/Chat";
2026-02-16 19:59:37 +00:00
import { SelectionScreen } from "./components/selection/SelectionScreen";
import { usePathCompletion } from "./components/selection/usePathCompletion";
import "./App.css";
function App() {
2026-02-16 18:57:39 +00:00
const [projectPath, setProjectPath] = React.useState<string | null>(null);
const [errorMsg, setErrorMsg] = React.useState<string | null>(null);
const [pathInput, setPathInput] = React.useState("");
const [isOpening, setIsOpening] = React.useState(false);
const [knownProjects, setKnownProjects] = React.useState<string[]>([]);
const [homeDir, setHomeDir] = React.useState<string | null>(null);
2026-02-16 18:57:39 +00:00
React.useEffect(() => {
api
.getKnownProjects()
.then((projects) => setKnownProjects(projects))
.catch((error) => console.error(error));
}, []);
React.useEffect(() => {
let active = true;
api
.getHomeDirectory()
.then((home) => {
if (!active) return;
setHomeDir(home);
setPathInput((current) => {
if (current.trim()) {
return current;
}
const initial = home.endsWith("/") ? home : `${home}/`;
return initial;
});
})
.catch((error) => {
console.error(error);
});
return () => {
active = false;
};
}, []);
2026-02-16 19:59:37 +00:00
const {
matchList,
selectedMatch,
suggestionTail,
completionError,
currentPartial,
setSelectedMatch,
acceptSelectedMatch,
acceptMatch,
closeSuggestions,
} = usePathCompletion({
pathInput,
setPathInput,
homeDir,
listDirectoryAbsolute: api.listDirectoryAbsolute,
});
2026-02-16 18:57:39 +00:00
async function openProject(path: string) {
if (!path.trim()) {
setErrorMsg("Please enter a project path.");
return;
}
2026-02-16 18:57:39 +00:00
try {
setErrorMsg(null);
setIsOpening(true);
const confirmedPath = await api.openProject(path.trim());
setProjectPath(confirmedPath);
} catch (e) {
console.error(e);
const message =
e instanceof Error
? e.message
: typeof e === "string"
? e
: "An error occurred opening the project.";
setErrorMsg(message);
} finally {
setIsOpening(false);
}
}
2026-02-16 18:57:39 +00:00
function handleOpen() {
void openProject(pathInput);
}
2026-02-16 19:59:37 +00:00
async function handleForgetProject(path: string) {
try {
await api.forgetKnownProject(path);
setKnownProjects((prev) => prev.filter((p) => p !== path));
} catch (error) {
console.error(error);
}
}
2026-02-16 18:57:39 +00:00
async function closeProject() {
try {
await api.closeProject();
setProjectPath(null);
} catch (e) {
console.error(e);
}
}
2026-02-16 19:59:37 +00:00
function handlePathInputKeyDown(
event: React.KeyboardEvent<HTMLInputElement>,
) {
if (event.key === "ArrowDown") {
if (matchList.length > 0) {
event.preventDefault();
setSelectedMatch((selectedMatch + 1) % matchList.length);
}
} else if (event.key === "ArrowUp") {
if (matchList.length > 0) {
event.preventDefault();
setSelectedMatch(
(selectedMatch - 1 + matchList.length) % matchList.length,
);
}
} else if (event.key === "Tab") {
if (matchList.length > 0) {
event.preventDefault();
acceptSelectedMatch();
}
} else if (event.key === "Escape") {
event.preventDefault();
closeSuggestions();
} else if (event.key === "Enter") {
handleOpen();
}
}
2026-02-16 19:48:39 +00:00
2026-02-16 18:57:39 +00:00
return (
<main
className="container"
style={{ height: "100vh", padding: 0, maxWidth: "100%" }}
>
{!projectPath ? (
2026-02-16 19:59:37 +00:00
<SelectionScreen
knownProjects={knownProjects}
onOpenProject={openProject}
onForgetProject={handleForgetProject}
pathInput={pathInput}
onPathInputChange={setPathInput}
onPathInputKeyDown={handlePathInputKeyDown}
isOpening={isOpening}
suggestionTail={suggestionTail}
matchList={matchList}
selectedMatch={selectedMatch}
onSelectMatch={setSelectedMatch}
onAcceptMatch={acceptMatch}
onCloseSuggestions={closeSuggestions}
completionError={completionError}
currentPartial={currentPartial}
/>
2026-02-16 18:57:39 +00:00
) : (
<div className="workspace" style={{ height: "100%" }}>
<Chat projectPath={projectPath} onCloseProject={closeProject} />
</div>
)}
{errorMsg && (
<div className="error-message" style={{ marginTop: "20px" }}>
<p style={{ color: "red" }}>Error: {errorMsg}</p>
</div>
)}
</main>
);
}
export default App;