summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-07 19:17:49 +0000
committersoryu <soryu@soryu.co>2026-02-07 19:17:49 +0000
commit6b9b62404489fb55a2c52d7b75730be16efbec3e (patch)
tree1d750a2b09e518702ff7c63a9799e34ad481340f
parent27a91409a623347d148bc8d0f0fa81a955a038bd (diff)
downloadsoryu-6b9b62404489fb55a2c52d7b75730be16efbec3e.tar.gz
soryu-6b9b62404489fb55a2c52d7b75730be16efbec3e.zip
Add repo suggestions for directives
-rw-r--r--makima/frontend/src/routes/directives.tsx198
1 files changed, 170 insertions, 28 deletions
diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx
index 939961c..fd3808b 100644
--- a/makima/frontend/src/routes/directives.tsx
+++ b/makima/frontend/src/routes/directives.tsx
@@ -3,11 +3,19 @@ import { useParams, useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { DirectiveList } from "../components/directives/DirectiveList";
import { DirectiveDetail } from "../components/directives/DirectiveDetail";
+import { DirectoryInput } from "../components/mesh/DirectoryInput";
import { useDirectives } from "../hooks/useDirectives";
import { useAuth } from "../contexts/AuthContext";
+import {
+ getDaemonDirectories,
+ getRepositorySuggestions,
+} from "../lib/api";
import type {
DirectiveWithChains,
CreateDirectiveRequest,
+ RepositorySourceType,
+ DaemonDirectory,
+ RepositoryHistoryEntry,
} from "../lib/api";
export default function DirectivesPage() {
@@ -58,7 +66,52 @@ function DirectivesContent() {
const [showCreateForm, setShowCreateForm] = useState(false);
const [createTitle, setCreateTitle] = useState("");
const [createGoal, setCreateGoal] = useState("");
- const [createRepoUrl, setCreateRepoUrl] = useState("");
+
+ // Repository state
+ const [repoType, setRepoType] = useState<RepositorySourceType>("remote");
+ const [repoUrl, setRepoUrl] = useState("");
+ const [repoPath, setRepoPath] = useState("");
+ const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
+ const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
+ const [showRepoSuggestions, setShowRepoSuggestions] = useState(false);
+
+ // Fetch repository suggestions when modal opens and repo type changes
+ useEffect(() => {
+ if (showCreateForm && (repoType === "remote" || repoType === "local")) {
+ getRepositorySuggestions(repoType, undefined, 10)
+ .then((res) => {
+ setRepoSuggestions(res.entries);
+ setShowRepoSuggestions(res.entries.length > 0);
+ })
+ .catch(() => {
+ setRepoSuggestions([]);
+ setShowRepoSuggestions(false);
+ });
+ } else {
+ setRepoSuggestions([]);
+ setShowRepoSuggestions(false);
+ }
+ }, [showCreateForm, repoType]);
+
+ // Fetch daemon directories when "local" repo type is selected
+ useEffect(() => {
+ if (repoType === "local" && showCreateForm) {
+ getDaemonDirectories()
+ .then((res) => setSuggestedDirectories(res.directories))
+ .catch(() => setSuggestedDirectories([]));
+ }
+ }, [repoType, showCreateForm]);
+
+ // Apply a repository suggestion
+ const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => {
+ if (suggestion.repositoryUrl) {
+ setRepoUrl(suggestion.repositoryUrl);
+ }
+ if (suggestion.localPath) {
+ setRepoPath(suggestion.localPath);
+ }
+ setShowRepoSuggestions(false);
+ }, []);
// Load directive when ID changes
useEffect(() => {
@@ -84,6 +137,15 @@ function DirectivesContent() {
navigate("/directives");
}, [navigate]);
+ const resetCreateForm = useCallback(() => {
+ setShowCreateForm(false);
+ setCreateTitle("");
+ setCreateGoal("");
+ setRepoType("remote");
+ setRepoUrl("");
+ setRepoPath("");
+ }, []);
+
const handleCreate = useCallback(async () => {
if (!createTitle.trim() || !createGoal.trim()) return;
@@ -91,18 +153,17 @@ function DirectivesContent() {
title: createTitle.trim(),
goal: createGoal.trim(),
};
- if (createRepoUrl.trim()) {
- data.repositoryUrl = createRepoUrl.trim();
+ if (repoType === "remote" && repoUrl.trim()) {
+ data.repositoryUrl = repoUrl.trim();
+ } else if (repoType === "local" && repoPath.trim()) {
+ data.localPath = repoPath.trim();
}
const result = await saveDirective(data);
if (result) {
- setShowCreateForm(false);
- setCreateTitle("");
- setCreateGoal("");
- setCreateRepoUrl("");
+ resetCreateForm();
}
- }, [createTitle, createGoal, createRepoUrl, saveDirective]);
+ }, [createTitle, createGoal, repoType, repoUrl, repoPath, saveDirective, resetCreateForm]);
const handleDelete = useCallback(
async (directiveId: string) => {
@@ -118,7 +179,6 @@ function DirectivesContent() {
async (directiveId: string) => {
const ok = await startDirective(directiveId);
if (ok) {
- // Refresh the detail view
const updated = await fetchDirective(directiveId);
if (updated) {
setSelectedDirective(updated);
@@ -145,27 +205,30 @@ function DirectivesContent() {
</div>
)}
- {/* Create directive form */}
+ {/* Create directive modal */}
{showCreateForm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
<div className="w-full max-w-lg max-h-[90vh] overflow-y-auto p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)]">
<h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4">
New Directive
</h3>
- <div className="space-y-3">
+ <div className="space-y-4">
+ {/* Title */}
<div>
<label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
Title
</label>
<input
type="text"
- placeholder="Title"
+ placeholder="Directive title"
value={createTitle}
onChange={(e) => setCreateTitle(e.target.value)}
className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none"
autoFocus
/>
</div>
+
+ {/* Goal */}
<div>
<label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
Goal
@@ -178,26 +241,105 @@ function DirectivesContent() {
className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none resize-none"
/>
</div>
- <div>
- <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
- Repository URL (optional)
+
+ {/* Repository Configuration */}
+ <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
+ <label className="block font-mono text-xs text-[#75aafc] uppercase mb-3">
+ Repository (optional)
</label>
- <input
- type="text"
- placeholder="https://github.com/user/repo.git"
- value={createRepoUrl}
- onChange={(e) => setCreateRepoUrl(e.target.value)}
- className="w-full px-3 py-2 font-mono text-sm text-[#dbe7ff] bg-[#0d1b2d] border border-[#3f6fb3] focus:border-[#75aafc] outline-none"
- />
+
+ {/* Repository type selector */}
+ <div className="flex gap-2 mb-3">
+ <button
+ type="button"
+ onClick={() => setRepoType("remote")}
+ className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
+ repoType === "remote"
+ ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
+ : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
+ }`}
+ >
+ Remote
+ </button>
+ <button
+ type="button"
+ onClick={() => setRepoType("local")}
+ className={`flex-1 px-3 py-2 font-mono text-xs uppercase transition-colors ${
+ repoType === "local"
+ ? "bg-[#0f3c78] text-[#dbe7ff] border border-[#75aafc]"
+ : "bg-[#0d1b2d] text-[#8b949e] border border-[#3f6fb3] hover:border-[#75aafc]"
+ }`}
+ >
+ Local
+ </button>
+ </div>
+
+ {/* Repository suggestions */}
+ {showRepoSuggestions && repoSuggestions.length > 0 && (
+ <div className="mb-3">
+ <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
+ Recent Repositories
+ </label>
+ <div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto">
+ {repoSuggestions.map((suggestion) => (
+ <button
+ key={suggestion.id}
+ type="button"
+ onClick={() => applyRepoSuggestion(suggestion)}
+ className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
+ >
+ <div className="flex items-center justify-between">
+ <span className="text-[#9bc3ff] truncate">{suggestion.name}</span>
+ <span className="text-[10px] text-[#556677] ml-2">
+ {suggestion.useCount}&times;
+ </span>
+ </div>
+ <div className="text-[10px] text-[#556677] truncate">
+ {repoType === "local" ? suggestion.localPath : suggestion.repositoryUrl}
+ </div>
+ </button>
+ ))}
+ </div>
+ </div>
+ )}
+
+ {/* Repository URL (for remote) */}
+ {repoType === "remote" && (
+ <div>
+ <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
+ Repository URL
+ </label>
+ <input
+ type="text"
+ value={repoUrl}
+ onChange={(e) => setRepoUrl(e.target.value)}
+ placeholder="https://github.com/user/repo.git"
+ className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
+ />
+ </div>
+ )}
+
+ {/* Repository path (for local) */}
+ {repoType === "local" && (
+ <div>
+ <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
+ Local Path
+ </label>
+ <DirectoryInput
+ value={repoPath}
+ onChange={setRepoPath}
+ suggestions={suggestedDirectories}
+ placeholder="/path/to/repository"
+ repoUrl={repoUrl || null}
+ />
+ </div>
+ )}
</div>
+
+ {/* Actions */}
<div className="flex gap-2 justify-end pt-2">
<button
- onClick={() => {
- setShowCreateForm(false);
- setCreateTitle("");
- setCreateGoal("");
- setCreateRepoUrl("");
- }}
+ onClick={resetCreateForm}
className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
>
Cancel