@hienlh/ppm 0.2.20 → 0.2.21
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/CHANGELOG.md +31 -0
- package/CLAUDE.md +18 -1
- package/bun.lock +57 -59
- package/dist/ppm +0 -0
- package/dist/web/assets/chat-tab-C_U7EwM9.js +6 -0
- package/dist/web/assets/code-editor-DuarTBEe.js +1 -0
- package/dist/web/assets/diff-viewer-sBWBgb7U.js +4 -0
- package/dist/web/assets/git-graph-fOKEZiot.js +1 -0
- package/dist/web/assets/index-3zt5mBwZ.css +2 -0
- package/dist/web/assets/index-CaUQy3Zs.js +21 -0
- package/dist/web/assets/input-CTnwfHVN.js +41 -0
- package/dist/web/assets/settings-tab-C5aWMqIA.js +1 -0
- package/dist/web/assets/{terminal-tab-sGlqTp7k.js → terminal-tab-BEFAYT4S.js} +1 -1
- package/dist/web/assets/use-monaco-theme-BxaccPmI.js +11 -0
- package/dist/web/index.html +35 -8
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +13 -8
- package/docs/project-roadmap.md +22 -4
- package/docs/system-architecture.md +59 -0
- package/package.json +6 -14
- package/src/providers/claude-agent-sdk.ts +2 -2
- package/src/providers/registry.ts +12 -11
- package/src/server/routes/projects.ts +43 -0
- package/src/server/routes/settings.ts +42 -8
- package/src/server/ws/chat.ts +2 -2
- package/src/services/config.service.ts +5 -1
- package/src/services/project.service.ts +1 -0
- package/src/types/config.ts +37 -0
- package/src/types/project.ts +1 -0
- package/src/web/Users/hienlh/Projects/ppm/dist/web/monacoeditorwork/css.worker.bundle.js +54268 -0
- package/src/web/Users/hienlh/Projects/ppm/dist/web/monacoeditorwork/editor.worker.bundle.js +14316 -0
- package/src/web/Users/hienlh/Projects/ppm/dist/web/monacoeditorwork/html.worker.bundle.js +30452 -0
- package/src/web/Users/hienlh/Projects/ppm/dist/web/monacoeditorwork/json.worker.bundle.js +22095 -0
- package/src/web/Users/hienlh/Projects/ppm/dist/web/monacoeditorwork/ts.worker.bundle.js +225957 -0
- package/src/web/app.tsx +43 -5
- package/src/web/components/chat/chat-history-panel.tsx +106 -0
- package/src/web/components/chat/chat-tab.tsx +27 -19
- package/src/web/components/editor/code-editor.tsx +78 -197
- package/src/web/components/editor/diff-viewer.tsx +59 -176
- package/src/web/components/layout/add-project-form.tsx +151 -0
- package/src/web/components/layout/command-palette.tsx +3 -1
- package/src/web/components/layout/editor-panel.tsx +6 -4
- package/src/web/components/layout/mobile-drawer.tsx +48 -180
- package/src/web/components/layout/mobile-nav.tsx +89 -6
- package/src/web/components/layout/panel-layout.tsx +16 -10
- package/src/web/components/layout/project-bar.tsx +329 -0
- package/src/web/components/layout/project-bottom-sheet.tsx +345 -0
- package/src/web/components/layout/sidebar.tsx +56 -142
- package/src/web/components/layout/tab-bar.tsx +1 -6
- package/src/web/components/layout/tab-content.tsx +0 -10
- package/src/web/components/ui/dialog.tsx +1 -1
- package/src/web/lib/project-avatar.ts +45 -0
- package/src/web/lib/project-palette.ts +18 -0
- package/src/web/lib/use-monaco-theme.ts +29 -0
- package/src/web/stores/panel-store.ts +96 -9
- package/src/web/stores/project-store.ts +87 -3
- package/src/web/stores/settings-store.ts +31 -4
- package/src/web/stores/tab-store.ts +0 -2
- package/vite.config.ts +6 -2
- package/dist/web/assets/arrow-up-from-line-DjfWTP75.js +0 -1
- package/dist/web/assets/button-CQ5h5gxS.js +0 -41
- package/dist/web/assets/chat-tab-Cfw__7vJ.js +0 -6
- package/dist/web/assets/code-editor-D8Pz69sx.js +0 -2
- package/dist/web/assets/dialog-BL9i7XEo.js +0 -5
- package/dist/web/assets/diff-viewer-CWS5n7ur.js +0 -4
- package/dist/web/assets/dist-0XHv8Vwc.js +0 -1
- package/dist/web/assets/dist-Ca3N8Xbh.js +0 -46
- package/dist/web/assets/git-graph-DwA62J8-.js +0 -1
- package/dist/web/assets/git-status-panel-DaB-zzSF.js +0 -1
- package/dist/web/assets/index-BYIXPY6U.css +0 -2
- package/dist/web/assets/index-DbTCLiox.js +0 -17
- package/dist/web/assets/project-list-Z4lhtp6P.js +0 -1
- package/dist/web/assets/refresh-cw-S6I91MHO.js +0 -1
- package/dist/web/assets/settings-tab-BW6MGcir.js +0 -1
- package/dist/web/assets/trash-2-CGlFXde_.js +0 -1
- package/dist/web/assets/x-C0Rw5Giw.js +0 -1
- /package/dist/web/assets/{api-client-DzH9zCD7.js → api-client-BCjah751.js} +0 -0
- /package/dist/web/assets/{columns-2-DsiY76NQ.js → columns-2-DFQ3yid7.js} +0 -0
- /package/dist/web/assets/{copy-D_Q54D-v.js → copy-B-kLwqzg.js} +0 -0
- /package/dist/web/assets/{external-link-C6Y-D528.js → external-link-Dim3NH6h.js} +0 -0
- /package/dist/web/assets/{marked.esm-Cv8mjgnt.js → marked.esm-DhBtkBa8.js} +0 -0
- /package/dist/web/assets/{utils-D6me7KDg.js → utils-B-_GCz7E.js} +0 -0
package/src/web/app.tsx
CHANGED
|
@@ -3,8 +3,10 @@ import { Toaster } from "@/components/ui/sonner";
|
|
|
3
3
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
4
|
import { PanelLayout } from "@/components/layout/panel-layout";
|
|
5
5
|
import { Sidebar } from "@/components/layout/sidebar";
|
|
6
|
+
import { ProjectBar } from "@/components/layout/project-bar";
|
|
6
7
|
import { MobileNav } from "@/components/layout/mobile-nav";
|
|
7
8
|
import { MobileDrawer } from "@/components/layout/mobile-drawer";
|
|
9
|
+
import { ProjectBottomSheet } from "@/components/layout/project-bottom-sheet";
|
|
8
10
|
import { LoginScreen } from "@/components/auth/login-screen";
|
|
9
11
|
import { useProjectStore } from "@/stores/project-store";
|
|
10
12
|
import { useTabStore } from "@/stores/tab-store";
|
|
@@ -17,12 +19,17 @@ import { useUrlSync, parseUrlState } from "@/hooks/use-url-sync";
|
|
|
17
19
|
import { useGlobalKeybindings } from "@/hooks/use-global-keybindings";
|
|
18
20
|
import { useHealthCheck } from "@/hooks/use-health-check";
|
|
19
21
|
import { CommandPalette } from "@/components/layout/command-palette";
|
|
22
|
+
import { cn } from "@/lib/utils";
|
|
20
23
|
|
|
21
24
|
type AuthState = "checking" | "authenticated" | "unauthenticated";
|
|
22
25
|
|
|
23
26
|
export function App() {
|
|
24
27
|
const [authState, setAuthState] = useState<AuthState>("checking");
|
|
25
28
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
29
|
+
const [projectSheetOpen, setProjectSheetOpen] = useState(false);
|
|
30
|
+
const [mountedProjects, setMountedProjects] = useState<Set<string>>(
|
|
31
|
+
() => new Set(["__global__"]),
|
|
32
|
+
);
|
|
26
33
|
const theme = useSettingsStore((s) => s.theme);
|
|
27
34
|
const fetchProjects = useProjectStore((s) => s.fetchProjects);
|
|
28
35
|
const fetchServerInfo = useSettingsStore((s) => s.fetchServerInfo);
|
|
@@ -113,6 +120,15 @@ export function App() {
|
|
|
113
120
|
useTabStore.getState().switchProject(projectName);
|
|
114
121
|
}, [activeProject?.name]);
|
|
115
122
|
|
|
123
|
+
// Keep-alive: mount workspace on first visit, never unmount
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const projectName = activeProject?.name ?? "__global__";
|
|
126
|
+
setMountedProjects((prev) => {
|
|
127
|
+
if (prev.has(projectName)) return prev;
|
|
128
|
+
return new Set([...prev, projectName]);
|
|
129
|
+
});
|
|
130
|
+
}, [activeProject?.name]);
|
|
131
|
+
|
|
116
132
|
// On initial auth with no project selected, ensure a tab set exists
|
|
117
133
|
useEffect(() => {
|
|
118
134
|
if (authState === "authenticated" && !activeProject) {
|
|
@@ -138,22 +154,38 @@ export function App() {
|
|
|
138
154
|
return <LoginScreen onSuccess={handleLoginSuccess} />;
|
|
139
155
|
}
|
|
140
156
|
|
|
157
|
+
const activeProjectName = activeProject?.name ?? "__global__";
|
|
158
|
+
|
|
141
159
|
return (
|
|
142
160
|
<TooltipProvider>
|
|
143
161
|
<div className="h-dvh flex flex-col bg-background text-foreground overflow-hidden">
|
|
144
162
|
{/* Main layout */}
|
|
145
163
|
<div className="flex flex-1 overflow-hidden">
|
|
164
|
+
{/* Desktop project bar (far left, non-collapsible) */}
|
|
165
|
+
<ProjectBar />
|
|
166
|
+
|
|
146
167
|
{/* Desktop sidebar */}
|
|
147
168
|
<Sidebar />
|
|
148
169
|
|
|
149
|
-
{/* Content area */}
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
|
|
170
|
+
{/* Content area — keep-alive per project */}
|
|
171
|
+
{[...mountedProjects].map((projectName) => (
|
|
172
|
+
<div
|
|
173
|
+
key={projectName}
|
|
174
|
+
className={cn(
|
|
175
|
+
"flex-1 overflow-hidden pb-12 md:pb-0",
|
|
176
|
+
activeProjectName !== projectName && "hidden",
|
|
177
|
+
)}
|
|
178
|
+
>
|
|
179
|
+
<PanelLayout projectName={projectName} />
|
|
180
|
+
</div>
|
|
181
|
+
))}
|
|
153
182
|
</div>
|
|
154
183
|
|
|
155
184
|
{/* Mobile bottom nav */}
|
|
156
|
-
<MobileNav
|
|
185
|
+
<MobileNav
|
|
186
|
+
onMenuPress={() => setDrawerOpen(true)}
|
|
187
|
+
onProjectsPress={() => setProjectSheetOpen(true)}
|
|
188
|
+
/>
|
|
157
189
|
|
|
158
190
|
{/* Mobile drawer overlay */}
|
|
159
191
|
<MobileDrawer
|
|
@@ -161,6 +193,12 @@ export function App() {
|
|
|
161
193
|
onClose={() => setDrawerOpen(false)}
|
|
162
194
|
/>
|
|
163
195
|
|
|
196
|
+
{/* Mobile project bottom sheet */}
|
|
197
|
+
<ProjectBottomSheet
|
|
198
|
+
isOpen={projectSheetOpen}
|
|
199
|
+
onClose={() => setProjectSheetOpen(false)}
|
|
200
|
+
/>
|
|
201
|
+
|
|
164
202
|
{/* Command palette (Shift+Shift) */}
|
|
165
203
|
<CommandPalette open={paletteOpen} onClose={closePalette} />
|
|
166
204
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from "react";
|
|
2
|
+
import { MessageSquare, Loader2, RefreshCw } from "lucide-react";
|
|
3
|
+
import { api, projectUrl } from "@/lib/api-client";
|
|
4
|
+
import { useTabStore } from "@/stores/tab-store";
|
|
5
|
+
import type { SessionInfo } from "../../../types/chat";
|
|
6
|
+
|
|
7
|
+
interface ChatHistoryPanelProps {
|
|
8
|
+
projectName?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatDate(iso: string): string {
|
|
12
|
+
try {
|
|
13
|
+
return new Date(iso).toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
|
14
|
+
} catch {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ChatHistoryPanel({ projectName }: ChatHistoryPanelProps) {
|
|
20
|
+
const [sessions, setSessions] = useState<SessionInfo[]>([]);
|
|
21
|
+
const [loading, setLoading] = useState(false);
|
|
22
|
+
const [error, setError] = useState<string | null>(null);
|
|
23
|
+
const openTab = useTabStore((s) => s.openTab);
|
|
24
|
+
|
|
25
|
+
const load = useCallback(async () => {
|
|
26
|
+
if (!projectName) return;
|
|
27
|
+
setLoading(true);
|
|
28
|
+
setError(null);
|
|
29
|
+
try {
|
|
30
|
+
const data = await api.get<SessionInfo[]>(`${projectUrl(projectName)}/chat/sessions`);
|
|
31
|
+
setSessions(data);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
setError(e instanceof Error ? e.message : "Failed to load sessions");
|
|
34
|
+
} finally {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
}, [projectName]);
|
|
38
|
+
|
|
39
|
+
useEffect(() => { load(); }, [load]);
|
|
40
|
+
|
|
41
|
+
if (!projectName) {
|
|
42
|
+
return (
|
|
43
|
+
<div className="flex items-center justify-center h-32 text-xs text-text-subtle px-4 text-center">
|
|
44
|
+
Select a project to view chat history
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (loading) {
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex items-center justify-center h-24">
|
|
52
|
+
<Loader2 className="size-4 animate-spin text-text-subtle" />
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (error) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex flex-col items-center gap-2 p-4 text-xs text-text-subtle">
|
|
60
|
+
<span>{error}</span>
|
|
61
|
+
<button onClick={load} className="flex items-center gap-1 hover:text-text-secondary transition-colors">
|
|
62
|
+
<RefreshCw className="size-3" /> Retry
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (sessions.length === 0) {
|
|
69
|
+
return (
|
|
70
|
+
<div className="flex flex-col items-center justify-center h-32 gap-2 text-xs text-text-subtle">
|
|
71
|
+
<MessageSquare className="size-5" />
|
|
72
|
+
<span>No chat sessions yet</span>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function openSession(session: SessionInfo) {
|
|
78
|
+
openTab({
|
|
79
|
+
type: "chat",
|
|
80
|
+
title: session.title || "Chat",
|
|
81
|
+
projectId: projectName ?? null,
|
|
82
|
+
metadata: { projectName, sessionId: session.id },
|
|
83
|
+
closable: true,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex flex-col">
|
|
89
|
+
{sessions.map((session) => (
|
|
90
|
+
<button
|
|
91
|
+
key={session.id}
|
|
92
|
+
onClick={() => openSession(session)}
|
|
93
|
+
className="flex items-start gap-2 px-3 py-2 text-left hover:bg-surface-elevated transition-colors border-b border-border/50 last:border-0"
|
|
94
|
+
>
|
|
95
|
+
<MessageSquare className="size-3.5 shrink-0 mt-0.5 text-text-subtle" />
|
|
96
|
+
<div className="flex-1 min-w-0">
|
|
97
|
+
<p className="text-xs font-medium truncate text-text-primary">{session.title || "Untitled"}</p>
|
|
98
|
+
{session.updatedAt && (
|
|
99
|
+
<p className="text-[10px] text-text-subtle">{formatDate(session.updatedAt)}</p>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</button>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -4,12 +4,11 @@ import { api, projectUrl } from "@/lib/api-client";
|
|
|
4
4
|
import { useChat } from "@/hooks/use-chat";
|
|
5
5
|
import { useUsage } from "@/hooks/use-usage";
|
|
6
6
|
import { useTabStore } from "@/stores/tab-store";
|
|
7
|
-
import { useProjectStore } from "@/stores/project-store";
|
|
8
7
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
9
8
|
import { buildBugReport, openGithubIssue, copyToClipboard } from "@/lib/report-bug";
|
|
10
9
|
import { MessageList } from "./message-list";
|
|
11
10
|
import { MessageInput, type ChatAttachment } from "./message-input";
|
|
12
|
-
import {
|
|
11
|
+
import { Bot } from "lucide-react";
|
|
13
12
|
import { SlashCommandPicker, type SlashItem } from "./slash-command-picker";
|
|
14
13
|
import { FilePicker } from "./file-picker";
|
|
15
14
|
import { UsageBadge, UsageDetailPanel } from "./usage-badge";
|
|
@@ -53,13 +52,25 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
53
52
|
const [externalFiles, setExternalFiles] = useState<File[] | null>(null);
|
|
54
53
|
const dragCounterRef = useRef(0);
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
// Use tab's own project, not global activeProject (keep-alive: hidden tabs must not react to switches)
|
|
56
|
+
const projectName = (metadata?.projectName as string) ?? "";
|
|
57
57
|
const updateTab = useTabStore((s) => s.updateTab);
|
|
58
58
|
const version = useSettingsStore((s) => s.version);
|
|
59
59
|
|
|
60
|
+
// Fetch AI model name
|
|
61
|
+
const [modelName, setModelName] = useState<string>("");
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
api.get<{ default_provider: string; providers: Record<string, { model?: string }> }>("/api/settings/ai")
|
|
64
|
+
.then((ai) => {
|
|
65
|
+
const provider = ai.providers[ai.default_provider];
|
|
66
|
+
setModelName(provider?.model ?? ai.default_provider);
|
|
67
|
+
})
|
|
68
|
+
.catch(() => {});
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
60
71
|
// Usage runs independently — auto-refreshes on interval
|
|
61
72
|
const { usageInfo, usageLoading, lastUpdatedAt, refreshUsage, mergeUsage } =
|
|
62
|
-
useUsage(
|
|
73
|
+
useUsage(projectName, providerId);
|
|
63
74
|
|
|
64
75
|
// Persist sessionId and providerId to tab metadata so reload restores the session
|
|
65
76
|
useEffect(() => {
|
|
@@ -80,18 +91,17 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
80
91
|
reconnect,
|
|
81
92
|
refetchMessages,
|
|
82
93
|
isConnected,
|
|
83
|
-
} = useChat(sessionId, providerId,
|
|
94
|
+
} = useChat(sessionId, providerId, projectName, { onUsageEvent: mergeUsage });
|
|
84
95
|
|
|
85
96
|
const handleNewSession = useCallback(() => {
|
|
86
|
-
const projectName = activeProject?.name ?? null;
|
|
87
97
|
useTabStore.getState().openTab({
|
|
88
98
|
type: "chat",
|
|
89
99
|
title: "AI Chat",
|
|
90
100
|
metadata: { projectName },
|
|
91
|
-
projectId: projectName,
|
|
101
|
+
projectId: projectName || null,
|
|
92
102
|
closable: true,
|
|
93
103
|
});
|
|
94
|
-
}, [
|
|
104
|
+
}, [projectName]);
|
|
95
105
|
|
|
96
106
|
const handleSelectSession = useCallback((session: SessionInfo) => {
|
|
97
107
|
setSessionId(session.id);
|
|
@@ -127,7 +137,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
127
137
|
|
|
128
138
|
if (!sessionId) {
|
|
129
139
|
try {
|
|
130
|
-
const pName =
|
|
140
|
+
const pName = projectName;
|
|
131
141
|
const session = await api.post<Session>(`${projectUrl(pName)}/chat/sessions`, {
|
|
132
142
|
providerId,
|
|
133
143
|
title: content.slice(0, 50),
|
|
@@ -145,7 +155,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
145
155
|
}
|
|
146
156
|
sendMessage(fullContent);
|
|
147
157
|
},
|
|
148
|
-
[sessionId, providerId,
|
|
158
|
+
[sessionId, providerId, projectName, sendMessage, buildMessageWithAttachments],
|
|
149
159
|
);
|
|
150
160
|
|
|
151
161
|
// --- Slash picker handlers ---
|
|
@@ -243,19 +253,17 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
243
253
|
pendingApproval={pendingApproval}
|
|
244
254
|
onApprovalResponse={respondToApproval}
|
|
245
255
|
isStreaming={isStreaming}
|
|
246
|
-
projectName={
|
|
256
|
+
projectName={projectName}
|
|
247
257
|
/>
|
|
248
258
|
|
|
249
259
|
{/* Bottom toolbar */}
|
|
250
260
|
<div className="border-t border-border bg-background shrink-0">
|
|
251
261
|
{/* Session bar */}
|
|
252
262
|
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border/50">
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
projectName={activeProject?.name}
|
|
258
|
-
/>
|
|
263
|
+
<div className="flex items-center gap-1.5 text-xs text-text-secondary px-1">
|
|
264
|
+
<Bot className="size-3.5" />
|
|
265
|
+
<span className="truncate max-w-[180px]">{modelName || "AI"}</span>
|
|
266
|
+
</div>
|
|
259
267
|
<div className="flex items-center gap-2">
|
|
260
268
|
<UsageBadge
|
|
261
269
|
usage={usageInfo}
|
|
@@ -265,7 +273,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
265
273
|
{sessionId && (
|
|
266
274
|
<button
|
|
267
275
|
onClick={async () => {
|
|
268
|
-
const text = await buildBugReport(version, { sessionId, projectName:
|
|
276
|
+
const text = await buildBugReport(version, { sessionId, projectName: projectName });
|
|
269
277
|
setBugReportText(text);
|
|
270
278
|
setCopied(false);
|
|
271
279
|
}}
|
|
@@ -323,7 +331,7 @@ export function ChatTab({ metadata, tabId }: ChatTabProps) {
|
|
|
323
331
|
onSend={handleSend}
|
|
324
332
|
isStreaming={isStreaming}
|
|
325
333
|
onCancel={cancelStreaming}
|
|
326
|
-
projectName={
|
|
334
|
+
projectName={projectName}
|
|
327
335
|
onSlashStateChange={handleSlashStateChange}
|
|
328
336
|
onSlashItemsLoaded={setSlashItems}
|
|
329
337
|
slashSelected={slashSelected}
|