@genui/a3-create 0.1.36
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/README.md +123 -0
- package/dist/index.js +684 -0
- package/package.json +52 -0
- package/template/.cursor/rules/example-app.mdc +9 -0
- package/template/CLAUDE.md +121 -0
- package/template/README.md +20 -0
- package/template/_gitignore +36 -0
- package/template/app/ThemeProvider.tsx +17 -0
- package/template/app/agents/age.ts +25 -0
- package/template/app/agents/greeting.ts +30 -0
- package/template/app/agents/index.ts +57 -0
- package/template/app/agents/onboarding/index.ts +15 -0
- package/template/app/agents/onboarding/prompt.ts +59 -0
- package/template/app/agents/registry.ts +17 -0
- package/template/app/agents/state.ts +10 -0
- package/template/app/api/agui/route.ts +56 -0
- package/template/app/api/chat/route.ts +35 -0
- package/template/app/api/stream/route.ts +57 -0
- package/template/app/apple-icon-dark.png +0 -0
- package/template/app/apple-icon.png +0 -0
- package/template/app/components/atoms/AgentNode.tsx +56 -0
- package/template/app/components/atoms/AppLogo.tsx +44 -0
- package/template/app/components/atoms/ChatContainer.tsx +13 -0
- package/template/app/components/atoms/ChatHeader.tsx +49 -0
- package/template/app/components/atoms/MarkdownRenderer.tsx +134 -0
- package/template/app/components/atoms/MessageBubble.tsx +21 -0
- package/template/app/components/atoms/TransitionEdge.tsx +49 -0
- package/template/app/components/atoms/index.ts +7 -0
- package/template/app/components/molecules/ChatInput.tsx +94 -0
- package/template/app/components/molecules/ChatMessage.tsx +45 -0
- package/template/app/components/molecules/index.ts +2 -0
- package/template/app/components/organisms/AgentGraph.tsx +75 -0
- package/template/app/components/organisms/AguiChat.tsx +133 -0
- package/template/app/components/organisms/Chat.tsx +88 -0
- package/template/app/components/organisms/ChatMessageList.tsx +35 -0
- package/template/app/components/organisms/ExamplePageLayout.tsx +118 -0
- package/template/app/components/organisms/OnboardingChat.tsx +24 -0
- package/template/app/components/organisms/Sidebar.tsx +147 -0
- package/template/app/components/organisms/SidebarLayout.tsx +58 -0
- package/template/app/components/organisms/StateViewer.tsx +126 -0
- package/template/app/components/organisms/StreamChat.tsx +173 -0
- package/template/app/components/organisms/index.ts +10 -0
- package/template/app/constants/chat.ts +52 -0
- package/template/app/constants/paths.ts +1 -0
- package/template/app/constants/ui.ts +61 -0
- package/template/app/examples/agui/page.tsx +26 -0
- package/template/app/examples/chat/page.tsx +26 -0
- package/template/app/examples/page.tsx +106 -0
- package/template/app/examples/stream/page.tsx +26 -0
- package/template/app/favicon-dark.ico +0 -0
- package/template/app/favicon.ico +0 -0
- package/template/app/icon.svg +13 -0
- package/template/app/layout.tsx +36 -0
- package/template/app/lib/actions/restartSession.ts +10 -0
- package/template/app/lib/getAgentGraphData.ts +43 -0
- package/template/app/lib/getGraphLayout.ts +99 -0
- package/template/app/lib/hooks/useRestart.ts +33 -0
- package/template/app/lib/parseTransitionTargets.ts +140 -0
- package/template/app/lib/providers/anthropic.ts +12 -0
- package/template/app/lib/providers/bedrock.ts +12 -0
- package/template/app/lib/providers/openai.ts +10 -0
- package/template/app/onboarding/page.tsx +21 -0
- package/template/app/page.tsx +16 -0
- package/template/app/styled.d.ts +6 -0
- package/template/app/theme.ts +22 -0
- package/template/docs/A3-README.md +121 -0
- package/template/docs/API-REFERENCE.md +85 -0
- package/template/docs/ARCHITECTURE.md +84 -0
- package/template/docs/CORE-CONCEPTS.md +347 -0
- package/template/docs/CUSTOM_LOGGING.md +36 -0
- package/template/docs/CUSTOM_PROVIDERS.md +642 -0
- package/template/docs/CUSTOM_STORES.md +228 -0
- package/template/docs/PROVIDER-ANTHROPIC.md +45 -0
- package/template/docs/PROVIDER-BEDROCK.md +45 -0
- package/template/docs/PROVIDER-OPENAI.md +47 -0
- package/template/docs/PROVIDERS.md +124 -0
- package/template/docs/QUICK-START-EXAMPLES.md +197 -0
- package/template/docs/RESILIENCE.md +226 -0
- package/template/docs/TRANSITIONS.md +245 -0
- package/template/docs/WIDGETS.md +331 -0
- package/template/docs/contributing/LOGGING.md +104 -0
- package/template/docs/designs/a3-gtm-strategy.md +280 -0
- package/template/docs/designs/a3-platform-vision.md +276 -0
- package/template/next-env.d.ts +6 -0
- package/template/next.config.mjs +15 -0
- package/template/package.json +41 -0
- package/template/public/android-chrome-192x192.png +0 -0
- package/template/public/android-chrome-512x512.png +0 -0
- package/template/public/site.webmanifest +11 -0
- package/template/scripts/dev.mjs +29 -0
- package/template/tsconfig.json +47 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useRef, useMemo } from 'react'
|
|
4
|
+
import { ChatMessageList } from '@organisms/ChatMessageList'
|
|
5
|
+
import { ChatContainer, ChatHeader } from '@atoms'
|
|
6
|
+
import { ChatInput } from '@molecules'
|
|
7
|
+
import { MessageSender } from '@genui/a3'
|
|
8
|
+
import type { Message } from '@genui/a3'
|
|
9
|
+
import { HttpAgent, EventType } from '@ag-ui/client'
|
|
10
|
+
import { CHAT_ERROR, CHAT_ERROR_SHORT } from '@constants/ui'
|
|
11
|
+
import { useRestart, type RestartResult } from '@lib/hooks/useRestart'
|
|
12
|
+
|
|
13
|
+
interface AguiChatProps {
|
|
14
|
+
sessionId: string
|
|
15
|
+
initialMessages?: Message[]
|
|
16
|
+
onSessionUpdate?: (update: { activeAgentId: string | null; state: Record<string, unknown> }) => void
|
|
17
|
+
onRestart?: () => Promise<RestartResult>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function AguiChat({ sessionId, initialMessages, onSessionUpdate, onRestart }: AguiChatProps) {
|
|
21
|
+
const agent = useMemo(() => new HttpAgent({ url: '/api/agui', threadId: sessionId }), [sessionId])
|
|
22
|
+
const [messages, setMessages] = useState<Message[]>(initialMessages ?? [])
|
|
23
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
24
|
+
const assistantIdRef = useRef<string>('')
|
|
25
|
+
const { isRestarting, handleRestart } = useRestart({ onRestart, setMessages, onSessionUpdate })
|
|
26
|
+
|
|
27
|
+
const handleSubmit = useCallback(
|
|
28
|
+
async (text: string) => {
|
|
29
|
+
const userMsg: Message = {
|
|
30
|
+
messageId: crypto.randomUUID(),
|
|
31
|
+
text,
|
|
32
|
+
metadata: { source: MessageSender.USER },
|
|
33
|
+
}
|
|
34
|
+
setMessages((prev) => [...prev, userMsg])
|
|
35
|
+
setIsLoading(true)
|
|
36
|
+
|
|
37
|
+
let assistantId = crypto.randomUUID()
|
|
38
|
+
assistantIdRef.current = assistantId
|
|
39
|
+
|
|
40
|
+
const streamingMsg: Message = {
|
|
41
|
+
messageId: assistantId,
|
|
42
|
+
text: '',
|
|
43
|
+
metadata: { source: MessageSender.ASSISTANT },
|
|
44
|
+
isStreaming: true,
|
|
45
|
+
}
|
|
46
|
+
setMessages((prev) => [...prev, streamingMsg])
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const runId = crypto.randomUUID()
|
|
50
|
+
|
|
51
|
+
// Add the user message to the agent's internal state BEFORE running.
|
|
52
|
+
agent.addMessage({ id: crypto.randomUUID(), role: 'user', content: text })
|
|
53
|
+
|
|
54
|
+
await agent.runAgent(
|
|
55
|
+
{
|
|
56
|
+
runId,
|
|
57
|
+
tools: [],
|
|
58
|
+
context: [],
|
|
59
|
+
forwardedProps: {},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
onEvent({ event }) {
|
|
63
|
+
if (event.type === EventType.TEXT_MESSAGE_CONTENT && 'delta' in event) {
|
|
64
|
+
const delta = (event as unknown as { delta: string }).delta
|
|
65
|
+
setMessages((prev) =>
|
|
66
|
+
prev.map((m) => (m.messageId === assistantId ? { ...m, text: m.text + delta } : m)),
|
|
67
|
+
)
|
|
68
|
+
} else if (event.type === EventType.CUSTOM && 'name' in event) {
|
|
69
|
+
const customEvent = event as unknown as {
|
|
70
|
+
name: string
|
|
71
|
+
value?: { toAgentId?: string; state?: Record<string, unknown> }
|
|
72
|
+
}
|
|
73
|
+
if (customEvent.name === 'AgentTransition') {
|
|
74
|
+
onSessionUpdate?.({
|
|
75
|
+
activeAgentId: customEvent.value?.toAgentId ?? null,
|
|
76
|
+
state: customEvent.value?.state ?? {},
|
|
77
|
+
})
|
|
78
|
+
const prevAssistantId = assistantId
|
|
79
|
+
assistantId = crypto.randomUUID()
|
|
80
|
+
assistantIdRef.current = assistantId
|
|
81
|
+
setMessages((prev) => {
|
|
82
|
+
const updated = prev.map((m) =>
|
|
83
|
+
m.messageId === prevAssistantId ? { ...m, isStreaming: false } : m,
|
|
84
|
+
)
|
|
85
|
+
return [
|
|
86
|
+
...updated,
|
|
87
|
+
{
|
|
88
|
+
messageId: assistantId,
|
|
89
|
+
text: '',
|
|
90
|
+
metadata: { source: MessageSender.ASSISTANT },
|
|
91
|
+
isStreaming: true,
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
} else if (event.type === EventType.RUN_FINISHED) {
|
|
97
|
+
const result = (event as unknown as { result?: Record<string, unknown> }).result
|
|
98
|
+
onSessionUpdate?.({
|
|
99
|
+
activeAgentId: (result?.activeAgentId as string) ?? null,
|
|
100
|
+
state: (result?.state as Record<string, unknown>) ?? {},
|
|
101
|
+
})
|
|
102
|
+
setMessages((prev) => prev.map((m) => (m.messageId === assistantId ? { ...m, isStreaming: false } : m)))
|
|
103
|
+
} else if (event.type === EventType.RUN_ERROR) {
|
|
104
|
+
setMessages((prev) =>
|
|
105
|
+
prev.map((m) =>
|
|
106
|
+
m.messageId === assistantId ? { ...m, text: m.text || CHAT_ERROR_SHORT, isStreaming: false } : m,
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('AG-UI chat error:', error)
|
|
115
|
+
setMessages((prev) =>
|
|
116
|
+
prev.map((m) => (m.messageId === assistantId ? { ...m, text: m.text || CHAT_ERROR, isStreaming: false } : m)),
|
|
117
|
+
)
|
|
118
|
+
} finally {
|
|
119
|
+
setIsLoading(false)
|
|
120
|
+
setMessages((prev) => prev.map((m) => (m.messageId === assistantId ? { ...m, isStreaming: false } : m)))
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
[agent, onSessionUpdate],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<ChatContainer elevation={0}>
|
|
128
|
+
<ChatHeader onRestart={() => void handleRestart?.()} isRestarting={isRestarting} />
|
|
129
|
+
<ChatMessageList messages={messages} />
|
|
130
|
+
<ChatInput onSubmit={(text) => void handleSubmit(text)} disabled={isLoading || isRestarting} />
|
|
131
|
+
</ChatContainer>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import { CircularProgress } from '@mui/material'
|
|
5
|
+
import { ChatMessageList } from '@organisms/ChatMessageList'
|
|
6
|
+
import { ChatContainer, ChatHeader } from '@atoms'
|
|
7
|
+
import { ChatInput } from '@molecules'
|
|
8
|
+
import { MessageSender } from '@genui/a3'
|
|
9
|
+
import type { Message } from '@genui/a3'
|
|
10
|
+
import { CHAT_ERROR } from '@constants/ui'
|
|
11
|
+
import { useRestart, type RestartResult } from '@lib/hooks/useRestart'
|
|
12
|
+
|
|
13
|
+
type ChatApiResponse = {
|
|
14
|
+
response: string
|
|
15
|
+
activeAgentId: string | null
|
|
16
|
+
nextAgentId: string | null
|
|
17
|
+
state: Record<string, unknown>
|
|
18
|
+
goalAchieved: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ChatProps {
|
|
22
|
+
sessionId: string
|
|
23
|
+
initialMessages?: Message[]
|
|
24
|
+
onSessionUpdate?: (update: { activeAgentId: string | null; state: Record<string, unknown> }) => void
|
|
25
|
+
onRestart?: () => Promise<RestartResult>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Chat({ sessionId, initialMessages, onSessionUpdate, onRestart }: ChatProps) {
|
|
29
|
+
const [messages, setMessages] = useState<Message[]>(initialMessages ?? [])
|
|
30
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
31
|
+
const { isRestarting, handleRestart } = useRestart({ onRestart, setMessages, onSessionUpdate })
|
|
32
|
+
|
|
33
|
+
const handleSubmit = useCallback(
|
|
34
|
+
async (text: string) => {
|
|
35
|
+
const userMsg: Message = {
|
|
36
|
+
messageId: crypto.randomUUID(),
|
|
37
|
+
text,
|
|
38
|
+
metadata: { source: MessageSender.USER },
|
|
39
|
+
}
|
|
40
|
+
setMessages((prev) => [...prev, userMsg])
|
|
41
|
+
setIsLoading(true)
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const response = await fetch('/api/chat', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ message: text, sessionId }),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`HTTP error: ${response.status}`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = (await response.json()) as ChatApiResponse
|
|
55
|
+
|
|
56
|
+
onSessionUpdate?.({ activeAgentId: data.activeAgentId, state: data.state })
|
|
57
|
+
|
|
58
|
+
const assistantMsg: Message = {
|
|
59
|
+
messageId: crypto.randomUUID(),
|
|
60
|
+
text: data.response,
|
|
61
|
+
metadata: { source: MessageSender.ASSISTANT },
|
|
62
|
+
}
|
|
63
|
+
setMessages((prev) => [...prev, assistantMsg])
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Chat API error:', error)
|
|
66
|
+
const errorMsg: Message = {
|
|
67
|
+
messageId: crypto.randomUUID(),
|
|
68
|
+
text: CHAT_ERROR,
|
|
69
|
+
metadata: { source: MessageSender.ASSISTANT },
|
|
70
|
+
}
|
|
71
|
+
setMessages((prev) => [...prev, errorMsg])
|
|
72
|
+
} finally {
|
|
73
|
+
setIsLoading(false)
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[onSessionUpdate, sessionId],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<ChatContainer elevation={0}>
|
|
81
|
+
<ChatHeader onRestart={() => void handleRestart?.()} isRestarting={isRestarting}>
|
|
82
|
+
{isLoading && <CircularProgress size={16} />}
|
|
83
|
+
</ChatHeader>
|
|
84
|
+
<ChatMessageList messages={messages} />
|
|
85
|
+
<ChatInput onSubmit={handleSubmit} disabled={isLoading || isRestarting} />
|
|
86
|
+
</ChatContainer>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useRef, useEffect } from 'react'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
import { Box } from '@mui/material'
|
|
6
|
+
import type { Theme } from '@mui/material/styles'
|
|
7
|
+
import { ChatMessage } from '@molecules'
|
|
8
|
+
import type { Message } from '@genui/a3'
|
|
9
|
+
|
|
10
|
+
const MessageListContainer = styled(Box)`
|
|
11
|
+
flex: 1;
|
|
12
|
+
overflow-y: auto;
|
|
13
|
+
padding: ${({ theme }) => (theme as Theme).spacing(2)};
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: ${({ theme }) => (theme as Theme).spacing(1.5)};
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
export function ChatMessageList({ messages }: { messages: Message[] }) {
|
|
20
|
+
const bottomRef = useRef<HTMLDivElement>(null)
|
|
21
|
+
const lastMessage = messages[messages.length - 1]
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
25
|
+
}, [messages.length, lastMessage?.text])
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<MessageListContainer>
|
|
29
|
+
{messages.map((m) => (
|
|
30
|
+
<ChatMessage key={m.messageId} message={m} />
|
|
31
|
+
))}
|
|
32
|
+
<div ref={bottomRef} />
|
|
33
|
+
</MessageListContainer>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import { Box, Typography } from '@mui/material'
|
|
5
|
+
import { AgentGraph } from './AgentGraph'
|
|
6
|
+
import { Chat } from './Chat'
|
|
7
|
+
import { AguiChat } from './AguiChat'
|
|
8
|
+
import { StreamChat } from './StreamChat'
|
|
9
|
+
import { StateViewer } from './StateViewer'
|
|
10
|
+
import { restartSession } from '@lib/actions/restartSession'
|
|
11
|
+
import type { AgentInfo } from '@lib/getAgentGraphData'
|
|
12
|
+
import type { Message } from '@genui/a3'
|
|
13
|
+
|
|
14
|
+
type ExampleVariant = 'blocking' | 'stream' | 'agui'
|
|
15
|
+
|
|
16
|
+
interface ExamplePageLayoutProps {
|
|
17
|
+
title: string
|
|
18
|
+
description: string
|
|
19
|
+
sessionId: string
|
|
20
|
+
initialMessages: Message[]
|
|
21
|
+
variant: ExampleVariant
|
|
22
|
+
agents: AgentInfo[]
|
|
23
|
+
initialActiveAgentId?: string | null
|
|
24
|
+
initialState?: Record<string, unknown>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ExamplePageLayout({
|
|
28
|
+
title,
|
|
29
|
+
description,
|
|
30
|
+
sessionId,
|
|
31
|
+
initialMessages,
|
|
32
|
+
variant,
|
|
33
|
+
agents,
|
|
34
|
+
initialActiveAgentId,
|
|
35
|
+
initialState,
|
|
36
|
+
}: ExamplePageLayoutProps) {
|
|
37
|
+
const [activeAgentId, setActiveAgentId] = useState<string | null>(initialActiveAgentId ?? null)
|
|
38
|
+
const [state, setState] = useState<Record<string, unknown>>(initialState ?? {})
|
|
39
|
+
|
|
40
|
+
const handleSessionUpdate = useCallback(
|
|
41
|
+
(update: { activeAgentId: string | null; state: Record<string, unknown> }) => {
|
|
42
|
+
setActiveAgentId(update.activeAgentId)
|
|
43
|
+
setState(update.state)
|
|
44
|
+
},
|
|
45
|
+
[],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const handleRestart = useCallback(async () => {
|
|
49
|
+
const fresh = await restartSession(sessionId)
|
|
50
|
+
return {
|
|
51
|
+
messages: fresh.messages,
|
|
52
|
+
activeAgentId: fresh.activeAgentId,
|
|
53
|
+
state: fresh.state as Record<string, unknown>,
|
|
54
|
+
}
|
|
55
|
+
}, [sessionId])
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Box
|
|
59
|
+
sx={{
|
|
60
|
+
display: 'flex',
|
|
61
|
+
flexDirection: 'column',
|
|
62
|
+
height: '100%',
|
|
63
|
+
px: { xs: 2, sm: 3, md: 5, lg: 8 },
|
|
64
|
+
maxWidth: 1600,
|
|
65
|
+
width: '100%',
|
|
66
|
+
mx: 'auto',
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
<Typography variant="h5" fontWeight="bold" sx={{ pt: 3 }}>
|
|
70
|
+
{title}
|
|
71
|
+
</Typography>
|
|
72
|
+
<Typography variant="body1" color="text.secondary" sx={{ pt: 1, pb: 0, lineHeight: 1.6 }}>
|
|
73
|
+
{description}
|
|
74
|
+
</Typography>
|
|
75
|
+
<Box
|
|
76
|
+
sx={{
|
|
77
|
+
flex: 1,
|
|
78
|
+
py: 3,
|
|
79
|
+
minHeight: 0,
|
|
80
|
+
display: 'grid',
|
|
81
|
+
gridTemplateColumns: { xs: '1fr', md: '60% 40%' },
|
|
82
|
+
gap: 3,
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<Box sx={{ minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
|
86
|
+
{variant === 'blocking' && (
|
|
87
|
+
<Chat
|
|
88
|
+
sessionId={sessionId}
|
|
89
|
+
initialMessages={initialMessages}
|
|
90
|
+
onSessionUpdate={handleSessionUpdate}
|
|
91
|
+
onRestart={handleRestart}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
{variant === 'stream' && (
|
|
95
|
+
<StreamChat
|
|
96
|
+
sessionId={sessionId}
|
|
97
|
+
initialMessages={initialMessages}
|
|
98
|
+
onSessionUpdate={handleSessionUpdate}
|
|
99
|
+
onRestart={handleRestart}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
{variant === 'agui' && (
|
|
103
|
+
<AguiChat
|
|
104
|
+
sessionId={sessionId}
|
|
105
|
+
initialMessages={initialMessages}
|
|
106
|
+
onSessionUpdate={handleSessionUpdate}
|
|
107
|
+
onRestart={handleRestart}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
</Box>
|
|
111
|
+
<Box sx={{ minHeight: 0, display: 'flex', flexDirection: 'column', gap: 3, overflow: 'auto' }}>
|
|
112
|
+
<AgentGraph agents={agents} activeAgentId={activeAgentId} />
|
|
113
|
+
<StateViewer state={state} />
|
|
114
|
+
</Box>
|
|
115
|
+
</Box>
|
|
116
|
+
</Box>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react'
|
|
4
|
+
import { StreamChat } from './StreamChat'
|
|
5
|
+
import { restartSession } from '@lib/actions/restartSession'
|
|
6
|
+
import type { Message } from '@genui/a3'
|
|
7
|
+
|
|
8
|
+
interface OnboardingChatProps {
|
|
9
|
+
sessionId: string
|
|
10
|
+
initialMessages: Message[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function OnboardingChat({ sessionId, initialMessages }: OnboardingChatProps) {
|
|
14
|
+
const handleRestart = useCallback(async () => {
|
|
15
|
+
const fresh = await restartSession(sessionId)
|
|
16
|
+
return {
|
|
17
|
+
messages: fresh.messages,
|
|
18
|
+
activeAgentId: fresh.activeAgentId,
|
|
19
|
+
state: fresh.state as Record<string, unknown>,
|
|
20
|
+
}
|
|
21
|
+
}, [sessionId])
|
|
22
|
+
|
|
23
|
+
return <StreamChat sessionId={sessionId} initialMessages={initialMessages} onRestart={handleRestart} />
|
|
24
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { usePathname } from 'next/navigation'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { Box, Drawer, List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
|
|
6
|
+
import HomeOutlined from '@mui/icons-material/HomeOutlined'
|
|
7
|
+
import SmartToyOutlined from '@mui/icons-material/SmartToyOutlined'
|
|
8
|
+
import CodeOutlined from '@mui/icons-material/CodeOutlined'
|
|
9
|
+
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'
|
|
10
|
+
import StreamIcon from '@mui/icons-material/Stream'
|
|
11
|
+
import ExtensionIcon from '@mui/icons-material/Extension'
|
|
12
|
+
import { AppLogo } from '@atoms'
|
|
13
|
+
import { SIDEBAR_WIDTH } from './SidebarLayout'
|
|
14
|
+
import { NAV_HOME, NAV_ONBOARDING, NAV_EXAMPLES, NAV_CHAT, NAV_STREAMING, NAV_AGUI } from '@constants/ui'
|
|
15
|
+
|
|
16
|
+
interface SidebarProps {
|
|
17
|
+
open: boolean
|
|
18
|
+
onClose: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Sidebar({ open, onClose }: SidebarProps) {
|
|
22
|
+
const pathname = usePathname()
|
|
23
|
+
|
|
24
|
+
const drawerContent = (
|
|
25
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
|
26
|
+
<Box sx={{ p: 2, display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
27
|
+
<AppLogo width={28} height={28} />
|
|
28
|
+
</Box>
|
|
29
|
+
|
|
30
|
+
<List component="nav" sx={{ px: 1 }}>
|
|
31
|
+
<ListItemButton
|
|
32
|
+
component={Link}
|
|
33
|
+
href="/"
|
|
34
|
+
selected={pathname === '/'}
|
|
35
|
+
onClick={onClose}
|
|
36
|
+
sx={{ borderRadius: 1, mb: 0.5 }}
|
|
37
|
+
>
|
|
38
|
+
<ListItemIcon sx={{ minWidth: 40 }}>
|
|
39
|
+
<HomeOutlined />
|
|
40
|
+
</ListItemIcon>
|
|
41
|
+
<ListItemText primary={NAV_HOME} />
|
|
42
|
+
</ListItemButton>
|
|
43
|
+
|
|
44
|
+
<ListItemButton
|
|
45
|
+
component={Link}
|
|
46
|
+
href="/onboarding"
|
|
47
|
+
selected={pathname === '/onboarding'}
|
|
48
|
+
onClick={onClose}
|
|
49
|
+
sx={{ borderRadius: 1, mb: 0.5 }}
|
|
50
|
+
>
|
|
51
|
+
<ListItemIcon sx={{ minWidth: 40 }}>
|
|
52
|
+
<SmartToyOutlined />
|
|
53
|
+
</ListItemIcon>
|
|
54
|
+
<ListItemText primary={NAV_ONBOARDING} />
|
|
55
|
+
</ListItemButton>
|
|
56
|
+
|
|
57
|
+
<ListItemButton
|
|
58
|
+
component={Link}
|
|
59
|
+
href="/examples"
|
|
60
|
+
selected={pathname === '/examples'}
|
|
61
|
+
onClick={onClose}
|
|
62
|
+
sx={{ borderRadius: 1, mb: 0.5 }}
|
|
63
|
+
>
|
|
64
|
+
<ListItemIcon sx={{ minWidth: 40 }}>
|
|
65
|
+
<CodeOutlined />
|
|
66
|
+
</ListItemIcon>
|
|
67
|
+
<ListItemText primary={NAV_EXAMPLES} />
|
|
68
|
+
</ListItemButton>
|
|
69
|
+
|
|
70
|
+
<List component="div" disablePadding>
|
|
71
|
+
<ListItemButton
|
|
72
|
+
component={Link}
|
|
73
|
+
href="/examples/chat"
|
|
74
|
+
selected={pathname === '/examples/chat'}
|
|
75
|
+
onClick={onClose}
|
|
76
|
+
sx={{ borderRadius: 1, mb: 0.5, pl: 4 }}
|
|
77
|
+
>
|
|
78
|
+
<ListItemIcon sx={{ minWidth: 40 }}>
|
|
79
|
+
<ChatBubbleOutlineIcon />
|
|
80
|
+
</ListItemIcon>
|
|
81
|
+
<ListItemText primary={NAV_CHAT} />
|
|
82
|
+
</ListItemButton>
|
|
83
|
+
|
|
84
|
+
<ListItemButton
|
|
85
|
+
component={Link}
|
|
86
|
+
href="/examples/stream"
|
|
87
|
+
selected={pathname === '/examples/stream'}
|
|
88
|
+
onClick={onClose}
|
|
89
|
+
sx={{ borderRadius: 1, mb: 0.5, pl: 4 }}
|
|
90
|
+
>
|
|
91
|
+
<ListItemIcon sx={{ minWidth: 40 }}>
|
|
92
|
+
<StreamIcon />
|
|
93
|
+
</ListItemIcon>
|
|
94
|
+
<ListItemText primary={NAV_STREAMING} />
|
|
95
|
+
</ListItemButton>
|
|
96
|
+
|
|
97
|
+
<ListItemButton
|
|
98
|
+
component={Link}
|
|
99
|
+
href="/examples/agui"
|
|
100
|
+
selected={pathname === '/examples/agui'}
|
|
101
|
+
onClick={onClose}
|
|
102
|
+
sx={{ borderRadius: 1, mb: 0.5, pl: 4 }}
|
|
103
|
+
>
|
|
104
|
+
<ListItemIcon sx={{ minWidth: 40 }}>
|
|
105
|
+
<ExtensionIcon />
|
|
106
|
+
</ListItemIcon>
|
|
107
|
+
<ListItemText primary={NAV_AGUI} />
|
|
108
|
+
</ListItemButton>
|
|
109
|
+
</List>
|
|
110
|
+
</List>
|
|
111
|
+
</Box>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<>
|
|
116
|
+
{/* Mobile drawer */}
|
|
117
|
+
<Drawer
|
|
118
|
+
variant="temporary"
|
|
119
|
+
open={open}
|
|
120
|
+
onClose={onClose}
|
|
121
|
+
ModalProps={{ keepMounted: true }}
|
|
122
|
+
sx={{
|
|
123
|
+
display: { xs: 'block', md: 'none' },
|
|
124
|
+
'& .MuiDrawer-paper': { width: SIDEBAR_WIDTH },
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
{drawerContent}
|
|
128
|
+
</Drawer>
|
|
129
|
+
|
|
130
|
+
{/* Desktop drawer */}
|
|
131
|
+
<Drawer
|
|
132
|
+
variant="permanent"
|
|
133
|
+
sx={{
|
|
134
|
+
display: { xs: 'none', md: 'block' },
|
|
135
|
+
'& .MuiDrawer-paper': {
|
|
136
|
+
width: SIDEBAR_WIDTH,
|
|
137
|
+
borderRight: 1,
|
|
138
|
+
borderColor: 'divider',
|
|
139
|
+
},
|
|
140
|
+
}}
|
|
141
|
+
open
|
|
142
|
+
>
|
|
143
|
+
{drawerContent}
|
|
144
|
+
</Drawer>
|
|
145
|
+
</>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useState } from 'react'
|
|
4
|
+
import { Box, IconButton, useMediaQuery, useTheme } from '@mui/material'
|
|
5
|
+
import MenuIcon from '@mui/icons-material/Menu'
|
|
6
|
+
import { AppLogo } from '@atoms'
|
|
7
|
+
import { Sidebar } from './Sidebar'
|
|
8
|
+
|
|
9
|
+
export const SIDEBAR_WIDTH = 260
|
|
10
|
+
|
|
11
|
+
interface SidebarLayoutProps {
|
|
12
|
+
children: ReactNode
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function SidebarLayout({ children }: SidebarLayoutProps) {
|
|
16
|
+
const theme = useTheme()
|
|
17
|
+
const isDesktop = useMediaQuery(theme.breakpoints.up('md'))
|
|
18
|
+
const [mobileOpen, setMobileOpen] = useState(false)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Box sx={{ display: 'flex', height: '100vh' }}>
|
|
22
|
+
<Sidebar open={mobileOpen} onClose={() => setMobileOpen(false)} />
|
|
23
|
+
|
|
24
|
+
<Box
|
|
25
|
+
component="main"
|
|
26
|
+
sx={{
|
|
27
|
+
flexGrow: 1,
|
|
28
|
+
display: 'flex',
|
|
29
|
+
flexDirection: 'column',
|
|
30
|
+
overflow: 'auto',
|
|
31
|
+
height: '100vh',
|
|
32
|
+
ml: { md: `${SIDEBAR_WIDTH}px` },
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{!isDesktop && (
|
|
36
|
+
<Box
|
|
37
|
+
sx={{
|
|
38
|
+
p: 1,
|
|
39
|
+
px: 2,
|
|
40
|
+
display: 'flex',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
gap: 1,
|
|
43
|
+
borderBottom: 1,
|
|
44
|
+
borderColor: 'divider',
|
|
45
|
+
bgcolor: 'background.paper',
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<IconButton onClick={() => setMobileOpen(true)} edge="start">
|
|
49
|
+
<MenuIcon />
|
|
50
|
+
</IconButton>
|
|
51
|
+
<AppLogo width={24} height={24} />
|
|
52
|
+
</Box>
|
|
53
|
+
)}
|
|
54
|
+
{children}
|
|
55
|
+
</Box>
|
|
56
|
+
</Box>
|
|
57
|
+
)
|
|
58
|
+
}
|