Skip to content

Commit 609af89

Browse files
committed
feat: 完成基本功能
1 parent c46ca03 commit 609af89

8 files changed

Lines changed: 198 additions & 171 deletions

File tree

src/render/NewTaskModal.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,31 @@ const NewTaskModal: React.FC<NewTaskModalProps> = ({
2323
const [workspacePath, setWorkspacePath] = useState(initialWorkspace || "");
2424
const [selectedPluginId, setSelectedPluginId] = useState<string>("custom");
2525
const [customCommand, setCustomCommand] = useState(initialAgentCommand);
26+
const [pluginInstallStatuses, setPluginInstallStatuses] = useState<Record<string, string>>({});
27+
28+
// Check install status for all plugins
29+
useEffect(() => {
30+
const checkAllPlugins = async () => {
31+
const statuses: Record<string, string> = {};
32+
for (const plugin of AGENT_PLUGINS) {
33+
try {
34+
if (plugin.checkCommand) {
35+
const res = await window.electron.invoke("agent:check-command", plugin.checkCommand);
36+
statuses[plugin.id] = res.installed ? "installed" : "not-installed";
37+
} else {
38+
statuses[plugin.id] = "installed";
39+
}
40+
} catch {
41+
statuses[plugin.id] = "not-installed";
42+
}
43+
}
44+
setPluginInstallStatuses(statuses);
45+
};
46+
47+
if (isOpen) {
48+
checkAllPlugins();
49+
}
50+
}, [isOpen]);
2651

2752
useEffect(() => {
2853
if (!isOpen) return;
@@ -147,7 +172,19 @@ const NewTaskModal: React.FC<NewTaskModalProps> = ({
147172
onClick={() => handlePluginChange(plugin.id)}
148173
className={`preset-button ${selectedPluginId === plugin.id ? "active" : ""}`}
149174
>
150-
{plugin.name}
175+
<div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
176+
<span>{plugin.name}</span>
177+
{pluginInstallStatuses[plugin.id] === "installed" && (
178+
<span style={{ fontSize: "0.7rem", color: "var(--text-secondary)" }}>
179+
(已安装)
180+
</span>
181+
)}
182+
{pluginInstallStatuses[plugin.id] === "not-installed" && (
183+
<span style={{ fontSize: "0.7rem", color: "var(--error)" }}>
184+
(未安装)
185+
</span>
186+
)}
187+
</div>
151188
</button>
152189
))}
153190
</div>

src/render/SettingsModal.tsx

Lines changed: 106 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Check, ChevronDown, Download, Loader2, Plus, RefreshCw, Trash2, X } from "lucide-react";
1+
import { Check, ChevronDown, Plus, Trash2, X } from "lucide-react";
22
import type React from "react";
33
import { useCallback, useEffect, useRef, useState } from "react";
44
import { AGENT_PLUGINS, getAgentPlugin } from "./agents/registry";
5-
import { useAgentInstall, useClickOutside, useEscapeKey, useNodeRuntime } from "./hooks";
5+
import { useClickOutside, useEscapeKey, useNodeRuntime } from "./hooks";
66

77
interface SettingsModalProps {
88
isOpen: boolean;
@@ -35,6 +35,9 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
3535
const [newEnvVal, setNewEnvVal] = useState("");
3636
const [activeTab, setActiveTab] = useState<"agents" | "general">("agents");
3737
const [isAgentDropdownOpen, setIsAgentDropdownOpen] = useState(false);
38+
const [pluginInstallStatuses, setPluginInstallStatuses] = useState<
39+
Record<string, string>
40+
>({});
3841

3942
const selectedPlugin = getAgentPlugin(selectedPluginId);
4043

@@ -53,13 +56,32 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
5356
restart,
5457
} = useNodeRuntime(isOpen);
5558

56-
const {
57-
installStatus,
58-
installedVersion,
59-
install: handleInstall,
60-
update: handleUpdate,
61-
uninstall: handleUninstall,
62-
} = useAgentInstall(selectedPlugin, isOpen, onAgentCommandChange);
59+
// Check install status for all plugins
60+
useEffect(() => {
61+
const checkAllPlugins = async () => {
62+
const statuses: Record<string, string> = {};
63+
for (const plugin of AGENT_PLUGINS) {
64+
try {
65+
if (plugin.checkCommand) {
66+
const res = await window.electron.invoke(
67+
"agent:check-command",
68+
plugin.checkCommand,
69+
);
70+
statuses[plugin.id] = res.installed ? "installed" : "not-installed";
71+
} else {
72+
statuses[plugin.id] = "installed";
73+
}
74+
} catch {
75+
statuses[plugin.id] = "not-installed";
76+
}
77+
}
78+
setPluginInstallStatuses(statuses);
79+
};
80+
81+
if (isOpen) {
82+
checkAllPlugins();
83+
}
84+
}, [isOpen]);
6385

6486
// Close handlers
6587
useEscapeKey(onClose, isOpen);
@@ -75,7 +97,8 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
7597
useEffect(() => {
7698
if (!isOpen) return;
7799
const found = AGENT_PLUGINS.find((plugin) => {
78-
const heuristic = plugin.checkCommand || plugin.defaultCommand.split(" ")[0];
100+
const heuristic =
101+
plugin.checkCommand || plugin.defaultCommand.split(" ")[0];
79102
return agentCommand.includes(heuristic);
80103
});
81104
setSelectedPluginId(found?.id ?? "custom");
@@ -107,18 +130,6 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
107130
[onAgentCommandChange],
108131
);
109132

110-
const handleAuthTerminal = useCallback(async () => {
111-
try {
112-
const cmd = selectedPlugin?.checkCommand || agentCommand.split(" ")[0] || agentCommand.trim();
113-
const res = await window.electron.invoke("agent:auth-terminal", cmd, currentWorkspace);
114-
if (!res?.success) {
115-
console.error(`Failed to launch terminal: ${res?.error || "unknown error"}`);
116-
}
117-
} catch (e: any) {
118-
console.error(`Failed to launch terminal: ${e.message}`);
119-
}
120-
}, [selectedPlugin, agentCommand, currentWorkspace]);
121-
122133
if (!isOpen) return null;
123134

124135
return (
@@ -156,19 +167,24 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
156167
<div className="settings-content">
157168
<div className="settings-content-header">
158169
<h2 className="settings-title">
159-
{activeTab === "agents" ? "Agents Configuration" : "General Settings"}
170+
{activeTab === "agents"
171+
? "Agents Configuration"
172+
: "General Settings"}
160173
</h2>
161174
<button type="button" onClick={onClose} className="modal-close-btn">
162175
<X size={20} />
163176
</button>
164177
</div>
165178

166179
{activeTab === "general" && (
167-
<div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
180+
<div
181+
style={{ display: "flex", flexDirection: "column", gap: "20px" }}
182+
>
168183
<div className="modal-section">
169184
<label className="modal-label">Node Runtime</label>
170185
<span className="modal-input-hint">
171-
Choose the runtime used to launch JS agents. Changes apply after restart.
186+
Choose the runtime used to launch JS agents. Changes apply
187+
after restart.
172188
</span>
173189
<div className="preset-buttons" style={{ maxWidth: "420px" }}>
174190
<button
@@ -189,7 +205,10 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
189205

190206
{nodeRuntime === "custom" && (
191207
<>
192-
<label className="modal-label" style={{ marginTop: "16px" }}>
208+
<label
209+
className="modal-label"
210+
style={{ marginTop: "16px" }}
211+
>
193212
Custom Node.js Path
194213
</label>
195214
<div
@@ -207,7 +226,11 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
207226
value={customNodePath}
208227
onChange={(e) => setCustomNodePath(e.target.value)}
209228
/>
210-
<button type="button" className="btn-secondary" onClick={browseNodePath}>
229+
<button
230+
type="button"
231+
className="btn-secondary"
232+
onClick={browseNodePath}
233+
>
211234
Browse...
212235
</button>
213236
</div>
@@ -218,7 +241,9 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
218241
)}
219242

220243
{runtimeError && (
221-
<div style={{ marginTop: "12px", color: "var(--error)" }}>{runtimeError}</div>
244+
<div style={{ marginTop: "12px", color: "var(--error)" }}>
245+
{runtimeError}
246+
</div>
222247
)}
223248
{runtimeSaved && !runtimeError && (
224249
<div
@@ -251,7 +276,10 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
251276
type="button"
252277
className="btn-primary"
253278
onClick={applyRuntime}
254-
disabled={runtimeSaving || (nodeRuntime === "custom" && !customNodePath.trim())}
279+
disabled={
280+
runtimeSaving ||
281+
(nodeRuntime === "custom" && !customNodePath.trim())
282+
}
255283
>
256284
{runtimeSaving ? "Saving..." : "Apply"}
257285
</button>
@@ -275,8 +303,9 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
275303
opacity: 0.9,
276304
}}
277305
>
278-
No Node.js runtime found. Some agents may fail to start. If using a custom
279-
path, verify it; otherwise ensure the bundled runtime is available.
306+
No Node.js runtime found. Some agents may fail to start.
307+
If using a custom path, verify it; otherwise ensure the
308+
bundled runtime is available.
280309
</div>
281310
</div>
282311
</div>
@@ -291,7 +320,9 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
291320
className="custom-select-trigger"
292321
onClick={() => setIsAgentDropdownOpen(!isAgentDropdownOpen)}
293322
>
294-
<span>{selectedPlugin ? selectedPlugin.name : "Custom Agent"}</span>
323+
<span>
324+
{selectedPlugin ? selectedPlugin.name : "Custom Agent"}
325+
</span>
295326
<ChevronDown
296327
size={16}
297328
className={`select-arrow ${isAgentDropdownOpen ? "open" : ""}`}
@@ -321,79 +352,47 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
321352
setIsAgentDropdownOpen(false);
322353
}}
323354
>
324-
{plugin.name}
325-
{selectedPluginId === plugin.id && <Check size={14} />}
355+
<div
356+
style={{
357+
display: "flex",
358+
alignItems: "center",
359+
gap: "8px",
360+
}}
361+
>
362+
{plugin.name}
363+
{pluginInstallStatuses[plugin.id] ===
364+
"installed" && (
365+
<span
366+
style={{
367+
fontSize: "0.75rem",
368+
color: "var(--text-secondary)",
369+
}}
370+
>
371+
(已安装)
372+
</span>
373+
)}
374+
{pluginInstallStatuses[plugin.id] ===
375+
"not-installed" && (
376+
<span
377+
style={{
378+
fontSize: "0.75rem",
379+
color: "var(--error)",
380+
}}
381+
>
382+
(未安装)
383+
</span>
384+
)}
385+
</div>
386+
{selectedPluginId === plugin.id && (
387+
<Check size={14} />
388+
)}
326389
</button>
327390
))}
328391
</div>
329392
)}
330393
</div>
331394
</div>
332395

333-
{/* Plugin Status & Install */}
334-
{selectedPlugin && selectedPlugin.packageSpec && (
335-
<div className="status-box" style={{ marginTop: 0, marginBottom: "20px" }}>
336-
<div className="status-info">
337-
<span className="status-label">Status:</span>
338-
{installStatus === "checking" && (
339-
<span className="status-text checking">Checking...</span>
340-
)}
341-
{installStatus === "installed" && (
342-
<span className="status-text installed">
343-
<Check size={16} /> Installed
344-
{installedVersion && (
345-
<span style={{ opacity: 0.7, fontSize: "0.85rem" }}>
346-
v{installedVersion}
347-
</span>
348-
)}
349-
</span>
350-
)}
351-
{installStatus === "not-installed" && (
352-
<span className="status-text not-installed">Not Installed</span>
353-
)}
354-
{installStatus === "installing" && (
355-
<span className="status-text processing">
356-
<Loader2 size={16} className="animate-spin" /> Installing...
357-
</span>
358-
)}
359-
{installStatus === "updating" && (
360-
<span className="status-text processing">
361-
<Loader2 size={16} className="animate-spin" /> Updating...
362-
</span>
363-
)}
364-
{installStatus === "uninstalling" && (
365-
<span className="status-text processing">
366-
<Loader2 size={16} className="animate-spin" /> Uninstalling...
367-
</span>
368-
)}
369-
</div>
370-
371-
{installStatus === "not-installed" && (
372-
<button type="button" onClick={handleInstall} className="btn-primary btn-small">
373-
<Download size={14} /> Install
374-
</button>
375-
)}
376-
{installStatus === "installed" && (
377-
<div style={{ display: "flex", gap: "8px" }}>
378-
<button
379-
type="button"
380-
onClick={handleUpdate}
381-
className="btn-primary btn-small"
382-
>
383-
<RefreshCw size={14} /> Update
384-
</button>
385-
<button
386-
type="button"
387-
onClick={handleUninstall}
388-
className="btn-danger-outline"
389-
>
390-
<Trash2 size={14} /> Uninstall
391-
</button>
392-
</div>
393-
)}
394-
</div>
395-
)}
396-
397396
{/* Command Input */}
398397
<div className="modal-section">
399398
<label htmlFor="agent-command" className="modal-label">
@@ -420,7 +419,12 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
420419
{Object.entries(agentEnv).map(([key, val]) => (
421420
<div key={key} className="env-row">
422421
<input readOnly value={key} className="env-input key" />
423-
<input readOnly value={val} type="password" className="env-input val" />
422+
<input
423+
readOnly
424+
value={val}
425+
type="password"
426+
className="env-input val"
427+
/>
424428
<button
425429
type="button"
426430
onClick={() => removeEnvVar(key)}
@@ -456,23 +460,13 @@ const SettingsModal: React.FC<SettingsModalProps> = ({
456460
</div>
457461

458462
<div className="modal-footer" style={{ marginTop: "auto" }}>
459-
{selectedPlugin && selectedPlugin.packageSpec && installStatus === "installed" ? (
460-
<div style={{ marginRight: "auto" }}>
461-
<button
462-
type="button"
463-
onClick={handleAuthTerminal}
464-
title="Open system terminal to login manually (type '/auth')"
465-
className="btn-secondary"
466-
>
467-
<span style={{ fontSize: "1.1em" }}>🔑</span> Authenticate in Terminal
468-
</button>
469-
</div>
470-
) : null}
471463
<button
472464
type="button"
473465
onClick={onConnectToggle}
474466
className="btn-primary"
475-
style={isConnected ? { backgroundColor: "#ef4444" } : undefined}
467+
style={
468+
isConnected ? { backgroundColor: "#ef4444" } : undefined
469+
}
476470
>
477471
{isConnected ? "Disconnect" : "Connect & Save"}
478472
</button>

0 commit comments

Comments
 (0)