Skip to content

Commit 8f2e6e8

Browse files
committed
feat: add ACP multi-pane agent workspace
1 parent 1bcda39 commit 8f2e6e8

12 files changed

Lines changed: 841 additions & 211 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
**anycode** is a lightning-fast web-based IDE that allows you to write, edit, and manage code directly from your browser. Built for speed and performance, anycode supports a wide range of programming languages and provides an intuitive interface with powerful features for a seamless development experience.
44

55
![editor](anycode/imgs/screen.png)
6+
![editor](anycode/imgs/screen_agents.png)
67

78
## Mobile Previews
89

anycode-base/src/styles.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,4 @@
298298

299299
.highlight.selected {
300300
background: rgba(210, 210, 210, 0.9);
301-
}
301+
}

anycode/App.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
background: rgba(45, 45, 45, 0.5);
115115
backdrop-filter: blur(2px);
116116
-webkit-backdrop-filter: blur(2px);
117+
backdrop-filter: blur(2px);
117118
/* border-bottom: 1px solid rgba(255, 255, 255, 0.1); */
118119
padding: 6px 16px;
119120
gap: 8px;

anycode/App.tsx

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
ensureDefaultAgents,
1919
updateAgents,
2020
} from './agents';
21-
import { AcpAgent, type AcpMessage, type AcpToolCall, type SearchMatch } from './types';
21+
import { AcpAgent, type SearchMatch } from './types';
2222
import './App.css';
2323
import {
2424
loadBottomVisible,
@@ -54,9 +54,6 @@ const App: React.FC = () => {
5454
const { wsRef, isConnected } = useSocket({});
5555

5656
const fileTree = useFileTree();
57-
const emptyToolCalls = useMemo<AcpToolCall[]>(() => [], []);
58-
const emptyMessages = useMemo<AcpMessage[]>(() => [], []);
59-
6057
const editors = useEditors({
6158
wsRef,
6259
isConnected,
@@ -265,10 +262,7 @@ const App: React.FC = () => {
265262
// }, []);
266263

267264
const sessionsArray = useMemo(() => Array.from(agents.acpSessions.values()), [agents.acpSessions]);
268-
const currentSession = useMemo(
269-
() => (agents.selectedAgentId ? agents.acpSessions.get(agents.selectedAgentId) ?? null : null),
270-
[agents.acpSessions, agents.selectedAgentId],
271-
);
265+
const availableAgents = useMemo<AcpAgent[]>(() => getAllAgents(), [agents.agentsVersion]);
272266
const defaultAgent = useMemo(() => getDefaultAgent(), [agents.agentsVersion]);
273267
const settingsAgents = useMemo<AcpAgent[]>(() => (
274268
agents.isAgentSettingsOpen ? getAllAgents() : []
@@ -279,15 +273,15 @@ const App: React.FC = () => {
279273
);
280274
const handleAddAgent = useCallback(() => {
281275
if (!defaultAgent) return;
282-
agents.startAgent(defaultAgent);
276+
return agents.startAgent(defaultAgent);
283277
}, [agents.startAgent, defaultAgent]);
278+
const handleStartSpecificAgent = useCallback((agent: AcpAgent) => {
279+
return agents.startAgent(agent);
280+
}, [agents.startAgent]);
284281
const handleOpenAgentSettings = useCallback(() => {
285282
ensureDefaultAgents();
286283
agents.setIsAgentSettingsOpen(true);
287284
}, [agents.setIsAgentSettingsOpen]);
288-
const handleCloseAgentPanel = useCallback(() => {
289-
setRightPanelVisible(false);
290-
}, []);
291285
const handleCloseAgentSettings = useCallback(() => {
292286
agents.setIsAgentSettingsOpen(false);
293287
}, [agents.setIsAgentSettingsOpen]);
@@ -403,24 +397,18 @@ const App: React.FC = () => {
403397
<AcpDialog
404398
key={`acp-${agents.agentsVersion}`}
405399
agents={sessionsArray}
400+
availableAgents={availableAgents}
406401
selectedAgentId={agents.selectedAgentId}
407402
onSelectAgent={agents.setSelectedAgentId}
408403
onCloseAgent={agents.closeAgent}
409404
onAddAgent={handleAddAgent}
405+
onStartAgent={handleStartSpecificAgent}
410406
onOpenSettings={handleOpenAgentSettings}
411-
agentId={currentSession?.agentId || defaultAgent?.id || 'gemini'}
412407
isOpen={true}
413-
onClose={handleCloseAgentPanel}
414408
onSendPrompt={agents.sendPrompt}
415409
onCancelPrompt={agents.cancelPrompt}
416410
onUndoPrompt={agents.undoPrompt}
417-
messages={currentSession?.messages || emptyMessages}
418-
toolCalls={emptyToolCalls}
419-
isConnected={currentSession ? (currentSession.isActive && isConnected) : false}
420-
isProcessing={currentSession?.isProcessing || false}
421-
modelSelector={currentSession?.modelSelector}
422-
reasoningSelector={currentSession?.reasoningSelector}
423-
contextUsage={currentSession?.contextUsage}
411+
isConnected={isConnected}
424412
onSelectModel={agents.setSessionModel}
425413
onSelectReasoning={agents.setSessionReasoning}
426414
showSettings={agents.isAgentSettingsOpen}

anycode/components/agent/AcpDialog.css

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,37 @@
115115
height: 25px;
116116
}
117117

118+
.acp-split-btn {
119+
background: transparent;
120+
border: none;
121+
color: var(--text-color, #ccc);
122+
border-radius: 4px;
123+
cursor: pointer;
124+
display: flex;
125+
align-items: center;
126+
justify-content: center;
127+
width: 28px;
128+
height: 26px;
129+
flex-shrink: 0;
130+
transition: background-color 0.2s;
131+
outline: none;
132+
}
133+
134+
.acp-split-btn:hover {
135+
background-color: var(--hover-bg, #2a2a2a);
136+
}
137+
138+
.acp-split-btn:focus,
139+
.acp-split-btn:active {
140+
outline: none;
141+
border: none;
142+
}
143+
144+
.acp-split-btn svg {
145+
width: 18px;
146+
height: 18px;
147+
}
148+
118149
.acp-settings-btn {
119150
background: transparent;
120151
border: none;
@@ -213,6 +244,17 @@
213244
height: 20px;
214245
}
215246

247+
.acp-split-btn {
248+
height: 36px;
249+
min-width: 36px;
250+
padding: 6px;
251+
}
252+
253+
.acp-split-btn svg {
254+
width: 20px;
255+
height: 20px;
256+
}
257+
216258
.acp-diff-btn,
217259
.acp-follow-btn {
218260
height: 36px;
@@ -233,6 +275,128 @@
233275
min-height: 0;
234276
}
235277

278+
.acp-workspace-content {
279+
overflow: hidden;
280+
}
281+
282+
.acp-workspace-content .sash,
283+
.acp-workspace-content .sash-module_sash__K-9lB {
284+
background: transparent;
285+
}
286+
287+
.acp-workspace-content .sash::after,
288+
.acp-workspace-content .sash-module_sash__K-9lB::after {
289+
content: '';
290+
position: absolute;
291+
pointer-events: none;
292+
}
293+
294+
.acp-workspace-content .sash.sash-vertical::after,
295+
.acp-workspace-content .sash-module_sash__K-9lB.sash-module_vertical__pB-rs::after {
296+
left: 50%;
297+
top: 0;
298+
bottom: 0;
299+
width: 2px;
300+
transform: translateX(-50%);
301+
background: linear-gradient(to bottom, transparent, #555, transparent);
302+
}
303+
304+
.acp-workspace-content .sash.sash-horizontal::after,
305+
.acp-workspace-content .sash-module_sash__K-9lB.sash-module_horizontal__kFbiw::after {
306+
left: 0;
307+
right: 0;
308+
top: 50%;
309+
height: 2px;
310+
transform: translateY(-50%);
311+
background: linear-gradient(to right, transparent, #555, transparent);
312+
}
313+
314+
.acp-pane-session,
315+
.acp-pane-empty {
316+
height: 100%;
317+
display: flex;
318+
flex-direction: column;
319+
min-height: 0;
320+
position: relative;
321+
}
322+
323+
.acp-pane-session {
324+
box-shadow: inset 0 0 0 1px transparent;
325+
}
326+
327+
.acp-pane-session.active,
328+
.acp-pane-empty.active {
329+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
330+
background: transparent;
331+
}
332+
333+
.acp-pane-session.active::before,
334+
.acp-pane-empty.active::before {
335+
content: '';
336+
position: absolute;
337+
top: 1px;
338+
left: 0;
339+
right: 0;
340+
height: 18px;
341+
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.05) 45%, transparent);
342+
pointer-events: none;
343+
z-index: 1;
344+
}
345+
346+
.acp-dialog-single-pane .acp-pane-session.active {
347+
box-shadow: inset 0 0 0 1px transparent;
348+
}
349+
350+
.acp-dialog-single-pane .acp-pane-session.active::before {
351+
content: none;
352+
}
353+
354+
.acp-pane-empty-subtitle {
355+
font-size: 11px;
356+
line-height: 1.2;
357+
color: #888;
358+
white-space: nowrap;
359+
overflow: hidden;
360+
text-overflow: ellipsis;
361+
}
362+
363+
.acp-pane-empty {
364+
align-items: center;
365+
justify-content: center;
366+
text-align: center;
367+
padding: 16px;
368+
}
369+
370+
.acp-pane-empty-title {
371+
font-size: 14px;
372+
color: var(--text-color, #ccc);
373+
margin-bottom: 4px;
374+
}
375+
376+
.acp-pane-empty-actions {
377+
display: flex;
378+
flex-wrap: wrap;
379+
justify-content: center;
380+
gap: 8px;
381+
margin-top: 12px;
382+
max-width: 100%;
383+
}
384+
385+
.acp-pane-empty-action {
386+
background: transparent;
387+
border: none;
388+
color: var(--text-color, #ccc);
389+
cursor: pointer;
390+
font-size: 12px;
391+
padding: 6px 10px;
392+
border-radius: 8px;
393+
transition: background-color 0.2s, color 0.2s;
394+
}
395+
396+
.acp-pane-empty-action:hover {
397+
background-color: var(--hover-bg, #2a2a2a);
398+
}
399+
236400
.acp-scroll-to-bottom-btn {
237401
position: absolute;
238402
right: 16px;

0 commit comments

Comments
 (0)