Skip to content

Commit c37187d

Browse files
committed
feat: add Workshop and Agent Builder views with sidebar integration
- Introduced new views for Workshop and Agent Builder, enhancing the application functionality. - Updated VIEW_ICONS and VIEW_ORDER to include the new views. - Added buttons in the workspace sidebar for easy navigation to Workshop and Agent Builder. - Implemented the AgentBuilderView component for configuring AI coding assistants.
1 parent d6d4057 commit c37187d

5 files changed

Lines changed: 144 additions & 16 deletions

File tree

app/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ const VIEW_ICONS: Record<string, { icon: string; label: string }> = {
9191
terminal: { icon: 'lucide:terminal', label: 'Terminal' },
9292
kanban: { icon: 'lucide:kanban', label: 'Kanban' },
9393
mcp: { icon: 'lucide:plug', label: 'MCP' },
94+
workshop: { icon: 'lucide:flask-conical', label: 'Workshop' },
95+
'agent-builder': { icon: 'lucide:bot', label: 'Agent Builder' },
9496
}
9597

9698
/** Primary view cycle: Chat → Editor → Terminal */
@@ -136,9 +138,7 @@ export default function EditorLayout() {
136138
const terminalStartupCommand = useCenteredTerminal ? 'openclaw tui' : undefined
137139
const mobileViewTabs = useMemo(() => {
138140
// On mobile, curate tabs to useful views + always include settings
139-
const mobile = visibleViews.filter(
140-
(v) => !['preview', 'diff', 'skills', 'prompts'].includes(v),
141-
)
141+
const mobile = visibleViews.filter((v) => !['preview', 'diff', 'skills', 'prompts'].includes(v))
142142
if (!mobile.includes('terminal')) mobile.push('terminal')
143143
return mobile.slice(0, 5)
144144
}, [visibleViews])

components/view-router.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ const SplitPreviewChat = dynamic(
4444
() => import('@/components/split-preview-chat').then((m) => m.SplitPreviewChat),
4545
{ ssr: false },
4646
)
47+
const WorkshopView = dynamic(
48+
() => import('@/components/views/workshop-view').then((m) => m.WorkshopView),
49+
{ ssr: false },
50+
)
51+
const AgentBuilderView = dynamic(
52+
() => import('@/components/views/agent-builder-view').then((m) => m.AgentBuilderView),
53+
{ ssr: false },
54+
)
4755

4856
const VIEW_ICONS: Record<string, { label: string }> = {
4957
chat: { label: 'Chat' },
@@ -55,6 +63,8 @@ const VIEW_ICONS: Record<string, { label: string }> = {
5563
prompts: { label: 'Prompts' },
5664
settings: { label: 'Settings' },
5765
terminal: { label: 'Terminal' },
66+
workshop: { label: 'Workshop' },
67+
'agent-builder': { label: 'Agent Builder' },
5868
}
5969

6070
const viewVariants = {
@@ -118,10 +128,7 @@ export function ViewRouter() {
118128
fallbackLabel={`${VIEW_ICONS[activeView]?.label ?? activeView} failed to render`}
119129
>
120130
{showSplitView ? (
121-
<SplitPreviewChat
122-
previewUrl={previewUrl}
123-
onClose={() => setPreviewDocked(false)}
124-
/>
131+
<SplitPreviewChat previewUrl={previewUrl} onClose={() => setPreviewDocked(false)} />
125132
) : (
126133
<>
127134
{activeView === 'chat' && <AgentPanel />}
@@ -131,6 +138,8 @@ export function ViewRouter() {
131138
{activeView === 'kanban' && <KanbanView />}
132139
{activeView === 'skills' && <SkillsView />}
133140
{activeView === 'prompts' && <PromptLibraryView />}
141+
{activeView === 'workshop' && <WorkshopView />}
142+
{activeView === 'agent-builder' && <AgentBuilderView />}
134143
{activeView === 'settings' && <SettingsPanel onBack={() => setView('chat')} />}
135144
{activeView === 'terminal' && (
136145
<div className="flex-1 flex min-h-0 min-w-0 overflow-hidden">
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use client'
2+
3+
import { useState, useCallback } from 'react'
4+
import { Icon } from '@iconify/react'
5+
import { AgentBuilder, AgentSummary } from '@/components/agent-builder'
6+
import { getAgentConfig, clearAgentConfig, type AgentConfig } from '@/lib/agent-session'
7+
import { useView } from '@/context/view-context'
8+
9+
export function AgentBuilderView() {
10+
const { setView } = useView()
11+
const [config, setConfig] = useState<AgentConfig | null>(() => getAgentConfig())
12+
const [configuring, setConfiguring] = useState(!config)
13+
14+
const handleComplete = useCallback((newConfig: AgentConfig) => {
15+
setConfig(newConfig)
16+
setConfiguring(false)
17+
}, [])
18+
19+
const handleReconfigure = useCallback(() => {
20+
setConfiguring(true)
21+
}, [])
22+
23+
const handleReset = useCallback(() => {
24+
clearAgentConfig()
25+
setConfig(null)
26+
setConfiguring(true)
27+
}, [])
28+
29+
return (
30+
<div className="flex-1 flex flex-col min-h-0 overflow-y-auto bg-[var(--sidebar-bg)]">
31+
<div className="shrink-0 flex items-center justify-between px-5 py-4 border-b border-[var(--border)]">
32+
<div className="flex items-center gap-3">
33+
<button
34+
onClick={() => setView('chat')}
35+
className="flex items-center justify-center w-8 h-8 rounded-lg hover:bg-[var(--bg-subtle)] text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors cursor-pointer"
36+
>
37+
<Icon icon="lucide:arrow-left" width={16} height={16} />
38+
</button>
39+
<div>
40+
<h2 className="text-[15px] font-semibold text-[var(--text-primary)]">Agent Builder</h2>
41+
<p className="text-[11px] text-[var(--text-tertiary)]">
42+
Configure your AI coding assistant
43+
</p>
44+
</div>
45+
</div>
46+
<div className="flex items-center gap-1.5">
47+
<span
48+
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium ${
49+
config && !configuring
50+
? 'bg-[color-mix(in_srgb,var(--success)_10%,transparent)] text-[var(--success)]'
51+
: 'bg-[var(--bg-subtle)] text-[var(--text-disabled)]'
52+
}`}
53+
>
54+
<Icon
55+
icon={config && !configuring ? 'lucide:check-circle-2' : 'lucide:circle-dashed'}
56+
width={10}
57+
height={10}
58+
/>
59+
{config && !configuring ? 'Active' : 'Setup'}
60+
</span>
61+
</div>
62+
</div>
63+
64+
<div className="flex-1 overflow-y-auto">
65+
<div className="max-w-[560px] mx-auto px-5 py-6">
66+
{configuring ? (
67+
<AgentBuilder onComplete={handleComplete} onSkip={() => setView('chat')} />
68+
) : config ? (
69+
<AgentSummary config={config} onReconfigure={handleReconfigure} onReset={handleReset} />
70+
) : null}
71+
</div>
72+
</div>
73+
</div>
74+
)
75+
}

components/workspace-sidebar.tsx

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,22 @@ export function WorkspaceSidebar({ collapsed, onToggle, repoName }: Props) {
214214
<Icon icon="lucide:kanban" width={24} height={24} />
215215
</button>
216216

217+
<button
218+
onClick={() => setView('workshop')}
219+
className={`activity-bar-btn ${activeView === 'workshop' ? 'activity-bar-btn--active' : ''}`}
220+
title="Agent Workshop"
221+
>
222+
<Icon icon="lucide:flask-conical" width={24} height={24} />
223+
</button>
224+
225+
<button
226+
onClick={() => setView('agent-builder')}
227+
className={`activity-bar-btn ${activeView === 'agent-builder' ? 'activity-bar-btn--active' : ''}`}
228+
title="Agent Builder"
229+
>
230+
<Icon icon="lucide:bot" width={24} height={24} />
231+
</button>
232+
217233
{/* Divider */}
218234
<div className="flex-1" />
219235
<div className="activity-bar-divider" />
@@ -283,15 +299,39 @@ export function WorkspaceSidebar({ collapsed, onToggle, repoName }: Props) {
283299

284300
{/* View Navigation */}
285301
<div className="mt-2 flex flex-col gap-0.5">
286-
{([
287-
{ id: 'chat' as const, icon: 'lucide:message-circle', label: 'Chat', shortcut: '⌘1' },
288-
{ id: 'editor' as const, icon: 'lucide:code', label: 'Editor', shortcut: '⌘2' },
289-
{ id: 'preview' as const, icon: 'lucide:eye', label: 'Preview', shortcut: '⌘3' },
290-
{ id: 'git' as const, icon: 'lucide:git-branch', label: 'Git', shortcut: '⌘4' },
291-
{ id: 'skills' as const, icon: 'lucide:wand-2', label: 'Skills', shortcut: '⌘5' },
292-
{ id: 'prompts' as const, icon: 'lucide:book-open', label: 'Prompts', shortcut: '⌘6' },
293-
{ id: 'kanban' as const, icon: 'lucide:kanban', label: 'Kanban', shortcut: '⌘7' },
294-
] as const).map((item) => (
302+
{(
303+
[
304+
{
305+
id: 'chat' as const,
306+
icon: 'lucide:message-circle',
307+
label: 'Chat',
308+
shortcut: '⌘1',
309+
},
310+
{ id: 'editor' as const, icon: 'lucide:code', label: 'Editor', shortcut: '⌘2' },
311+
{ id: 'preview' as const, icon: 'lucide:eye', label: 'Preview', shortcut: '⌘3' },
312+
{ id: 'git' as const, icon: 'lucide:git-branch', label: 'Git', shortcut: '⌘4' },
313+
{ id: 'skills' as const, icon: 'lucide:wand-2', label: 'Skills', shortcut: '⌘5' },
314+
{
315+
id: 'prompts' as const,
316+
icon: 'lucide:book-open',
317+
label: 'Prompts',
318+
shortcut: '⌘6',
319+
},
320+
{ id: 'kanban' as const, icon: 'lucide:kanban', label: 'Kanban', shortcut: '⌘7' },
321+
{
322+
id: 'workshop' as const,
323+
icon: 'lucide:flask-conical',
324+
label: 'Workshop',
325+
shortcut: '',
326+
},
327+
{
328+
id: 'agent-builder' as const,
329+
icon: 'lucide:bot',
330+
label: 'Agent Builder',
331+
shortcut: '',
332+
},
333+
] as const
334+
).map((item) => (
295335
<button
296336
key={item.id}
297337
onClick={() => setView(item.id)}

context/view-context.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export type ViewId =
2323
| 'mcp'
2424
| 'settings'
2525
| 'terminal'
26+
| 'workshop'
27+
| 'agent-builder'
2628

2729
const VIEW_ORDER: ViewId[] = [
2830
'chat',
@@ -33,6 +35,8 @@ const VIEW_ORDER: ViewId[] = [
3335
'skills',
3436
'prompts',
3537
'mcp',
38+
'workshop',
39+
'agent-builder',
3640
'settings',
3741
'terminal',
3842
]

0 commit comments

Comments
 (0)