@dyyz1993/create-agent 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/create.ts +31 -31
- package/src/commands/workspace.ts +112 -105
- package/src/lib/copy.ts +1 -0
- package/templates/agent/electron/main.js +46 -0
- package/templates/agent/electron/preload.js +5 -0
- package/templates/agent/electron-builder.json +40 -0
- package/templates/agent/eslint.config.mjs +2 -0
- package/templates/agent/package.json +60 -2
- package/templates/agent/src/mainview/App.tsx +29 -26
- package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +88 -88
- package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +105 -81
- package/templates/agent/src/mainview/components/search/SearchPanel.tsx +427 -378
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +3 -3
- package/templates/agent/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/agent/src/mainview/lib/api-client.ts +1 -4
- package/templates/agent/src/mainview/main.tsx +4 -10
- package/templates/agent/src/mainview/stores/use-feed-store.ts +107 -107
- package/templates/agent/src/mainview/utils/drop-handler.ts +114 -115
- package/templates/agent/src/server-config.ts +1 -1
- package/templates/agent/src/server.ts +1 -2
- package/templates/agent/src/shared/handlers/chat.ts +5 -5
- package/templates/agent/src/shared/handlers/debug.ts +5 -1
- package/templates/agent/src/shared/handlers/git.ts +286 -243
- package/templates/agent/src/shared/http-routes.ts +1 -1
- package/templates/agent/src/shared/lib/bash-security.ts +43 -43
- package/templates/agent/tsconfig.ipc.json +5 -1
- package/templates/agent/tsconfig.json +3 -1
- package/templates/chat/package.json +3 -0
- package/templates/chat/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/chat/src/mainview/lib/api-client.ts +2 -5
- package/templates/chat/src/mainview/main.tsx +10 -7
- package/templates/chat/src/server-config.ts +1 -1
- package/templates/chat/src/server.ts +1 -2
- package/templates/chat/src/shared/handlers/chat.ts +5 -5
- package/templates/chat/src/shared/handlers/debug.ts +5 -1
- package/templates/chat/src/shared/http-routes.ts +1 -1
- package/templates/chat/tsconfig.ipc.json +5 -1
- package/templates/chat/tsconfig.json +12 -2
- package/templates/general/package.json +3 -0
- package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +101 -81
- package/templates/general/src/mainview/components/search/SearchPanel.tsx +429 -378
- package/templates/general/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/general/src/mainview/lib/api-client.ts +2 -5
- package/templates/general/src/mainview/main.tsx +10 -7
- package/templates/general/src/mainview/stores/use-feed-store.ts +107 -107
- package/templates/general/src/mainview/utils/drop-handler.ts +114 -115
- package/templates/general/src/server-config.ts +1 -1
- package/templates/general/src/server.ts +1 -2
- package/templates/general/src/shared/handlers/chat.ts +5 -5
- package/templates/general/src/shared/handlers/debug.ts +5 -1
- package/templates/general/src/shared/handlers/git.ts +286 -243
- package/templates/general/src/shared/http-routes.ts +1 -1
- package/templates/general/tsconfig.ipc.json +5 -1
- package/templates/general/tsconfig.json +12 -2
- package/templates/shared/components/ErrorBoundary.tsx +50 -49
- package/templates/shared/http-routes.ts +210 -190
|
@@ -106,7 +106,7 @@ export function TodoPanel() {
|
|
|
106
106
|
) : (
|
|
107
107
|
<div className="space-y-2.5">
|
|
108
108
|
{items.map((item) => {
|
|
109
|
-
const cfg = statusConfig[item.status as TodoStatus]
|
|
109
|
+
const cfg = statusConfig[item.status as TodoStatus]!;
|
|
110
110
|
const Icon = cfg.icon;
|
|
111
111
|
return (
|
|
112
112
|
<div
|
|
@@ -115,7 +115,7 @@ export function TodoPanel() {
|
|
|
115
115
|
>
|
|
116
116
|
<div className="flex items-start gap-3">
|
|
117
117
|
<button
|
|
118
|
-
onClick={() => updateItem(item.id, nextStatus[item.status as TodoStatus])}
|
|
118
|
+
onClick={() => updateItem(item.id, nextStatus[item.status as TodoStatus]!)}
|
|
119
119
|
className={`flex-shrink-0 mt-0.5 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all ${cfg.circleColor} ${cfg.color} hover:scale-110`}
|
|
120
120
|
>
|
|
121
121
|
<Icon className="w-3 h-3" />
|
|
@@ -133,7 +133,7 @@ export function TodoPanel() {
|
|
|
133
133
|
<div className="flex items-center gap-1.5 mt-2 text-[11px] text-[var(--color-text-placeholder)]">
|
|
134
134
|
<span>{t("todo.status")}</span>
|
|
135
135
|
{statusFlow.map((s) => {
|
|
136
|
-
const sc = statusConfig[s]
|
|
136
|
+
const sc = statusConfig[s]!;
|
|
137
137
|
const isActive = s === item.status;
|
|
138
138
|
return (
|
|
139
139
|
<span
|
|
@@ -4,81 +4,90 @@ const HISTORY_KEY = "pi-input-history";
|
|
|
4
4
|
const MAX_ITEMS = 10;
|
|
5
5
|
|
|
6
6
|
function getStorageKey(sessionId: string): string {
|
|
7
|
-
|
|
7
|
+
return `${HISTORY_KEY}:${sessionId}`;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function readHistory(sessionId: string): string[] {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
try {
|
|
12
|
+
const raw = localStorage.getItem(getStorageKey(sessionId));
|
|
13
|
+
if (!raw) return [];
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
if (Array.isArray(parsed)) return parsed.slice(0, MAX_ITEMS);
|
|
16
|
+
} catch {
|
|
17
|
+
/* ignore */
|
|
18
|
+
}
|
|
19
|
+
return [];
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function writeHistory(sessionId: string, items: string[]) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(getStorageKey(sessionId), JSON.stringify(items.slice(0, MAX_ITEMS)));
|
|
25
|
+
} catch {
|
|
26
|
+
/* ignore */
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export function useInputHistory(sessionId: string) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
const historyRef = useRef<string[]>(readHistory(sessionId));
|
|
32
|
+
const indexRef = useRef(-1);
|
|
33
|
+
const [, forceUpdate] = useState(0);
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const hasPrev = historyRef.current.length > 0 && indexRef.current < historyRef.current.length - 1;
|
|
36
|
+
const hasNext = indexRef.current > 0;
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
const saveToHistory = useCallback(
|
|
39
|
+
(text: string) => {
|
|
40
|
+
const trimmed = text.trim();
|
|
41
|
+
if (!trimmed) return;
|
|
42
|
+
const h = historyRef.current;
|
|
43
|
+
const filtered = h.filter((item) => item !== trimmed);
|
|
44
|
+
const updated = [trimmed, ...filtered].slice(0, MAX_ITEMS);
|
|
45
|
+
historyRef.current = updated;
|
|
46
|
+
writeHistory(sessionId, updated);
|
|
47
|
+
indexRef.current = -1;
|
|
48
|
+
forceUpdate((n) => n + 1);
|
|
49
|
+
},
|
|
50
|
+
[sessionId]
|
|
51
|
+
);
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
const navigatePrev = useCallback((): string | null => {
|
|
54
|
+
const h = historyRef.current;
|
|
55
|
+
if (h.length === 0) return null;
|
|
56
|
+
const nextIdx = Math.min(indexRef.current + 1, h.length - 1);
|
|
57
|
+
indexRef.current = nextIdx;
|
|
58
|
+
forceUpdate((n) => n + 1);
|
|
59
|
+
return h[nextIdx] ?? null;
|
|
60
|
+
}, []);
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const navigateNext = useCallback((): string | null => {
|
|
63
|
+
const h = historyRef.current;
|
|
64
|
+
if (h.length === 0) return null;
|
|
65
|
+
const nextIdx = indexRef.current - 1;
|
|
66
|
+
if (nextIdx < 0) {
|
|
67
|
+
indexRef.current = -1;
|
|
68
|
+
forceUpdate((n) => n + 1);
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
indexRef.current = nextIdx;
|
|
72
|
+
forceUpdate((n) => n + 1);
|
|
73
|
+
return h[nextIdx] ?? null;
|
|
74
|
+
}, []);
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const clearHistory = useCallback(() => {
|
|
77
|
+
historyRef.current = [];
|
|
78
|
+
indexRef.current = -1;
|
|
79
|
+
try {
|
|
80
|
+
localStorage.removeItem(getStorageKey(sessionId));
|
|
81
|
+
} catch {
|
|
82
|
+
/* ignore */
|
|
83
|
+
}
|
|
84
|
+
forceUpdate((n) => n + 1);
|
|
85
|
+
}, [sessionId]);
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
const resetIndex = useCallback(() => {
|
|
88
|
+
indexRef.current = -1;
|
|
89
|
+
forceUpdate((n) => n + 1);
|
|
90
|
+
}, []);
|
|
82
91
|
|
|
83
|
-
|
|
92
|
+
return { saveToHistory, navigatePrev, navigateNext, clearHistory, resetIndex, hasPrev, hasNext };
|
|
84
93
|
}
|
|
@@ -42,12 +42,9 @@ class APIClientImpl {
|
|
|
42
42
|
|
|
43
43
|
const ipcTransport = new IPCTransport();
|
|
44
44
|
this._transport = "ipc";
|
|
45
|
-
this._baseUrl = null;
|
|
45
|
+
this._baseUrl = null;
|
|
46
46
|
this.client = createTypedClient<RPCMethods, RPCEvents>(ipcTransport);
|
|
47
47
|
this.setupElectrobunBridge(ipcTransport);
|
|
48
|
-
if (import.meta.env.DEV) {
|
|
49
|
-
console.warn("[APIClient] Desktop (IPC) initialized synchronously");
|
|
50
|
-
}
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/**
|
|
@@ -8,17 +8,11 @@ import App from "./App";
|
|
|
8
8
|
const isElectrobun = typeof window !== "undefined" && !!window.__electrobunBunBridge;
|
|
9
9
|
|
|
10
10
|
if (isElectrobun) {
|
|
11
|
-
|
|
12
|
-
apiClient.initSyncForDesktop();
|
|
13
|
-
}
|
|
14
|
-
// Web 端 WS 初始化由 App.tsx 统一管理,避免竞争
|
|
15
|
-
|
|
16
|
-
if (import.meta.env.DEV) {
|
|
17
|
-
console.warn("[Main] Application starting, isElectrobun:", isElectrobun);
|
|
11
|
+
apiClient.initSyncForDesktop();
|
|
18
12
|
}
|
|
19
13
|
|
|
20
14
|
createRoot(document.getElementById("root")!).render(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
<StrictMode>
|
|
16
|
+
<App />
|
|
17
|
+
</StrictMode>
|
|
24
18
|
);
|
|
@@ -3,53 +3,53 @@ import { apiClient } from "../lib/api-client";
|
|
|
3
3
|
import type { FeedCategory, FeedPost } from "../../shared/modules/feed";
|
|
4
4
|
|
|
5
5
|
interface FeedState {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
posts: FeedPost[];
|
|
7
|
+
author: string;
|
|
8
|
+
category: FeedCategory;
|
|
9
|
+
content: string;
|
|
10
|
+
loading: boolean;
|
|
11
|
+
|
|
12
|
+
setAuthor: (author: string) => void;
|
|
13
|
+
setCategory: (category: FeedCategory) => void;
|
|
14
|
+
setContent: (content: string) => void;
|
|
15
|
+
createPost: () => Promise<void>;
|
|
16
|
+
loadPosts: () => Promise<void>;
|
|
17
|
+
addPost: (post: FeedPost) => void;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const useFeedStore = create<FeedState>((set, get) => ({
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
21
|
+
posts: [],
|
|
22
|
+
author: "alice",
|
|
23
|
+
category: "tech",
|
|
24
|
+
content: "",
|
|
25
|
+
loading: false,
|
|
26
|
+
|
|
27
|
+
setAuthor: (author) => set({ author }),
|
|
28
|
+
setCategory: (category) => set({ category }),
|
|
29
|
+
setContent: (content) => set({ content }),
|
|
30
|
+
|
|
31
|
+
createPost: async () => {
|
|
32
|
+
const { content, category, author } = get();
|
|
33
|
+
if (!content.trim()) return;
|
|
34
|
+
set({ loading: true });
|
|
35
|
+
try {
|
|
36
|
+
await apiClient.call("feed.post", { content: content.trim(), category, author });
|
|
37
|
+
set({ content: "", loading: false });
|
|
38
|
+
} catch {
|
|
39
|
+
set({ loading: false });
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
loadPosts: async () => {
|
|
44
|
+
try {
|
|
45
|
+
const res = await apiClient.call("feed.list", { limit: 100 });
|
|
46
|
+
set({ posts: res.posts });
|
|
47
|
+
} catch {
|
|
48
|
+
// ignore
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
addPost: (post) => set((s) => ({ posts: [...s.posts, post] })),
|
|
53
53
|
}));
|
|
54
54
|
|
|
55
55
|
// --- Event Stream (订阅面板) ---
|
|
@@ -57,73 +57,73 @@ export const useFeedStore = create<FeedState>((set, get) => ({
|
|
|
57
57
|
export type SubEventType = "feed.update" | "chat.message" | "timer.tick";
|
|
58
58
|
|
|
59
59
|
interface EventEntry {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
id: string;
|
|
61
|
+
eventType: SubEventType;
|
|
62
|
+
payload: unknown;
|
|
63
|
+
filter: Record<string, unknown>;
|
|
64
|
+
timestamp: number;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
interface EventStreamState {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
entries: EventEntry[];
|
|
69
|
+
activeEventType: SubEventType;
|
|
70
|
+
activeFilter: string; // JSON string for the filter
|
|
71
|
+
subscriptionId: string | null;
|
|
72
|
+
subscribed: boolean;
|
|
73
|
+
|
|
74
|
+
setActiveEventType: (t: SubEventType) => void;
|
|
75
|
+
setActiveFilter: (f: string) => void;
|
|
76
|
+
handleSubscribe: () => Promise<void>;
|
|
77
|
+
handleUnsubscribe: () => void;
|
|
78
|
+
addEntry: (eventType: SubEventType, payload: unknown, filter: Record<string, unknown>) => void;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export const useEventStreamStore = create<EventStreamState>((set, get) => ({
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
82
|
+
entries: [],
|
|
83
|
+
activeEventType: "feed.update",
|
|
84
|
+
activeFilter: "",
|
|
85
|
+
subscriptionId: null,
|
|
86
|
+
subscribed: false,
|
|
87
|
+
|
|
88
|
+
setActiveEventType: (t) => set({ activeEventType: t }),
|
|
89
|
+
setActiveFilter: (f) => set({ activeFilter: f }),
|
|
90
|
+
|
|
91
|
+
handleSubscribe: async () => {
|
|
92
|
+
const { activeEventType, activeFilter } = get();
|
|
93
|
+
try {
|
|
94
|
+
const filter = activeFilter.trim() ? JSON.parse(activeFilter) : {};
|
|
95
|
+
const subId = await apiClient.subscribe(
|
|
96
|
+
activeEventType,
|
|
97
|
+
(payload: unknown) => {
|
|
98
|
+
get().addEntry(activeEventType, payload, filter);
|
|
99
|
+
},
|
|
100
|
+
filter
|
|
101
|
+
);
|
|
102
|
+
set({ subscriptionId: subId, subscribed: true });
|
|
103
|
+
} catch {
|
|
104
|
+
// subscription failed — UI stays unsubscribed
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
handleUnsubscribe: () => {
|
|
109
|
+
const { subscriptionId } = get();
|
|
110
|
+
if (subscriptionId) {
|
|
111
|
+
apiClient.unsubscribe(subscriptionId);
|
|
112
|
+
}
|
|
113
|
+
set({ subscriptionId: null, subscribed: false });
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
addEntry: (eventType, payload, filter) =>
|
|
117
|
+
set((s) => ({
|
|
118
|
+
entries: [
|
|
119
|
+
...s.entries.slice(-49),
|
|
120
|
+
{
|
|
121
|
+
id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`,
|
|
122
|
+
eventType,
|
|
123
|
+
payload,
|
|
124
|
+
filter,
|
|
125
|
+
timestamp: Date.now(),
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
})),
|
|
129
129
|
}));
|