@genui-a3/create 0.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/dist/index.js +98 -0
- package/package.json +45 -0
- package/template/README.md +16 -0
- package/template/_gitignore +35 -0
- package/template/app/(pages)/agui/AguiChat.tsx +109 -0
- package/template/app/(pages)/agui/page.tsx +12 -0
- package/template/app/(pages)/chat/page.tsx +12 -0
- package/template/app/(pages)/stream/page.tsx +12 -0
- package/template/app/ThemeProvider.tsx +17 -0
- package/template/app/agents/age.ts +24 -0
- package/template/app/agents/greeting.ts +37 -0
- package/template/app/api/agui/route.ts +69 -0
- package/template/app/api/chat/route.ts +65 -0
- package/template/app/api/stream/route.ts +64 -0
- package/template/app/apple-icon-dark.png +0 -0
- package/template/app/apple-icon.png +0 -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 +14 -0
- package/template/app/components/atoms/MessageBubble.tsx +21 -0
- package/template/app/components/atoms/index.ts +4 -0
- package/template/app/components/molecules/ChatInput.tsx +66 -0
- package/template/app/components/molecules/ChatMessage.tsx +41 -0
- package/template/app/components/molecules/index.ts +2 -0
- package/template/app/components/organisms/Chat.tsx +77 -0
- package/template/app/components/organisms/ChatMessageList.tsx +37 -0
- package/template/app/components/organisms/PageLayout.tsx +43 -0
- package/template/app/components/organisms/StreamChat.tsx +149 -0
- package/template/app/components/organisms/index.ts +4 -0
- package/template/app/constants/chat.ts +4 -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 +32 -0
- package/template/app/page.tsx +171 -0
- package/template/app/styled.d.ts +6 -0
- package/template/app/theme.ts +22 -0
- package/template/app/types/index.ts +8 -0
- package/template/next.config.mjs +6 -0
- package/template/package.json +29 -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/tsconfig.json +41 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import { Paper } from '@mui/material'
|
|
5
|
+
|
|
6
|
+
export const ChatContainer = styled(Paper)`
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
height: 100%;
|
|
10
|
+
min-height: 0;
|
|
11
|
+
border-radius: 12px;
|
|
12
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
13
|
+
`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import styled from 'styled-components'
|
|
4
|
+
import type { Theme } from '@mui/material/styles'
|
|
5
|
+
|
|
6
|
+
export const ChatHeader = styled.div`
|
|
7
|
+
border-bottom: 1px solid ${({ theme }) => (theme as Theme).palette.divider};
|
|
8
|
+
background-color: ${({ theme }) => (theme as Theme).palette.background.paper};
|
|
9
|
+
padding: ${({ theme }) => (theme as Theme).spacing(2, 3)};
|
|
10
|
+
flex-shrink: 0;
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
align-items: center;
|
|
14
|
+
`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
import { Paper } from '@mui/material'
|
|
3
|
+
import type { Theme } from '@mui/material/styles'
|
|
4
|
+
|
|
5
|
+
export const MessageBubble = styled(Paper)<{ $isUser: boolean }>`
|
|
6
|
+
max-width: 80%;
|
|
7
|
+
padding: ${({ theme }) => (theme as Theme).spacing(1.5, 2)};
|
|
8
|
+
border-radius: ${({ theme }) => (theme as Theme).spacing(2.5)};
|
|
9
|
+
${({ $isUser, theme }) =>
|
|
10
|
+
$isUser
|
|
11
|
+
? `
|
|
12
|
+
background-color: ${(theme as Theme).palette.primary.main};
|
|
13
|
+
color: ${(theme as Theme).palette.primary.contrastText};
|
|
14
|
+
border-bottom-right-radius: ${(theme as Theme).spacing(0.5)};
|
|
15
|
+
`
|
|
16
|
+
: `
|
|
17
|
+
background-color: ${(theme as Theme).palette.grey[200]};
|
|
18
|
+
color: ${(theme as Theme).palette.text.primary};
|
|
19
|
+
border-bottom-left-radius: ${(theme as Theme).spacing(0.5)};
|
|
20
|
+
`}
|
|
21
|
+
`
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
import { TextField, Button, Box } from '@mui/material'
|
|
6
|
+
import type { Theme } from '@mui/material/styles'
|
|
7
|
+
import SendIcon from '@mui/icons-material/Send'
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
onSubmit: (text: string) => void | Promise<void>
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
placeholder?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const InputContainer = styled(Box)`
|
|
16
|
+
border-top: 1px solid ${({ theme }) => (theme as Theme).palette.divider};
|
|
17
|
+
background-color: ${({ theme }) => (theme as Theme).palette.background.paper};
|
|
18
|
+
padding: ${({ theme }) => (theme as Theme).spacing(2)};
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
const InputForm = styled.form`
|
|
23
|
+
display: flex;
|
|
24
|
+
gap: ${({ theme }) => (theme as Theme).spacing(1.5)};
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
export function ChatInput({ onSubmit, disabled, placeholder = 'Type a message...' }: Props) {
|
|
28
|
+
const [value, setValue] = useState('')
|
|
29
|
+
|
|
30
|
+
const handleSubmit = useCallback(
|
|
31
|
+
(e: React.FormEvent) => {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
const trimmed = value.trim()
|
|
34
|
+
if (!trimmed || disabled) return
|
|
35
|
+
void onSubmit(trimmed)
|
|
36
|
+
setValue('')
|
|
37
|
+
},
|
|
38
|
+
[value, disabled, onSubmit],
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<InputContainer>
|
|
43
|
+
<InputForm onSubmit={handleSubmit}>
|
|
44
|
+
<TextField
|
|
45
|
+
fullWidth
|
|
46
|
+
value={value}
|
|
47
|
+
onChange={(e) => setValue(e.target.value)}
|
|
48
|
+
placeholder={placeholder}
|
|
49
|
+
disabled={disabled}
|
|
50
|
+
variant="outlined"
|
|
51
|
+
size="small"
|
|
52
|
+
data-testid="chat-input"
|
|
53
|
+
/>
|
|
54
|
+
<Button
|
|
55
|
+
type="submit"
|
|
56
|
+
variant="contained"
|
|
57
|
+
disabled={disabled || !value.trim()}
|
|
58
|
+
startIcon={<SendIcon />}
|
|
59
|
+
data-testid="chat-send"
|
|
60
|
+
>
|
|
61
|
+
Send
|
|
62
|
+
</Button>
|
|
63
|
+
</InputForm>
|
|
64
|
+
</InputContainer>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import styled, { keyframes } from 'styled-components'
|
|
2
|
+
import { Typography } from '@mui/material'
|
|
3
|
+
import { MessageBubble } from '@atoms'
|
|
4
|
+
import { MESSAGE_SENDER } from '@constants/chat'
|
|
5
|
+
import type { ChatMessage as ChatMessageType } from 'types'
|
|
6
|
+
|
|
7
|
+
type Props = { message: ChatMessageType }
|
|
8
|
+
|
|
9
|
+
const MessageRow = styled.div<{ $isUser: boolean }>`
|
|
10
|
+
display: flex;
|
|
11
|
+
justify-content: ${({ $isUser }) => ($isUser ? 'flex-end' : 'flex-start')};
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
const blink = keyframes`
|
|
15
|
+
0%, 100% { opacity: 1; }
|
|
16
|
+
50% { opacity: 0; }
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
const StreamingCursor = styled.span`
|
|
20
|
+
display: inline-block;
|
|
21
|
+
width: 6px;
|
|
22
|
+
height: 14px;
|
|
23
|
+
margin-left: 2px;
|
|
24
|
+
background-color: currentColor;
|
|
25
|
+
vertical-align: text-bottom;
|
|
26
|
+
animation: ${blink} 0.8s step-end infinite;
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
export function ChatMessage({ message }: Props) {
|
|
30
|
+
const isUser = message?.source === MESSAGE_SENDER.USER
|
|
31
|
+
return (
|
|
32
|
+
<MessageRow $isUser={isUser} data-testid="chat-message">
|
|
33
|
+
<MessageBubble $isUser={isUser} elevation={0}>
|
|
34
|
+
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
35
|
+
{message.body.trim()}
|
|
36
|
+
{message.isStreaming && <StreamingCursor />}
|
|
37
|
+
</Typography>
|
|
38
|
+
</MessageBubble>
|
|
39
|
+
</MessageRow>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import { Typography, CircularProgress } from '@mui/material'
|
|
5
|
+
import { ChatMessageList } from './ChatMessageList'
|
|
6
|
+
import { ChatContainer, ChatHeader } from '@atoms'
|
|
7
|
+
import { ChatInput } from '@molecules'
|
|
8
|
+
import type { ChatMessage as ChatMessageType } from 'types'
|
|
9
|
+
|
|
10
|
+
const SESSION_ID = 'demo-session'
|
|
11
|
+
|
|
12
|
+
type ChatApiResponse = {
|
|
13
|
+
response: string
|
|
14
|
+
activeAgentId: string | null
|
|
15
|
+
nextAgentId: string | null
|
|
16
|
+
state: Record<string, unknown>
|
|
17
|
+
goalAchieved: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Chat() {
|
|
21
|
+
const [messages, setMessages] = useState<ChatMessageType[]>([])
|
|
22
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
23
|
+
|
|
24
|
+
const handleSubmit = useCallback(async (text: string) => {
|
|
25
|
+
const userMsg: ChatMessageType = {
|
|
26
|
+
id: crypto.randomUUID(),
|
|
27
|
+
body: text,
|
|
28
|
+
source: 'user',
|
|
29
|
+
}
|
|
30
|
+
setMessages((prev) => [...prev, userMsg])
|
|
31
|
+
setIsLoading(true)
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch('/api/chat', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify({ message: text, sessionId: SESSION_ID }),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`HTTP error: ${response.status}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const data = (await response.json()) as ChatApiResponse
|
|
45
|
+
|
|
46
|
+
const assistantMsg: ChatMessageType = {
|
|
47
|
+
id: crypto.randomUUID(),
|
|
48
|
+
body: data.response,
|
|
49
|
+
source: 'assistant',
|
|
50
|
+
}
|
|
51
|
+
setMessages((prev) => [...prev, assistantMsg])
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Chat API error:', error)
|
|
54
|
+
const errorMsg: ChatMessageType = {
|
|
55
|
+
id: crypto.randomUUID(),
|
|
56
|
+
body: 'Sorry, something went wrong. Please try again.',
|
|
57
|
+
source: 'assistant',
|
|
58
|
+
}
|
|
59
|
+
setMessages((prev) => [...prev, errorMsg])
|
|
60
|
+
} finally {
|
|
61
|
+
setIsLoading(false)
|
|
62
|
+
}
|
|
63
|
+
}, [])
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<ChatContainer elevation={0}>
|
|
67
|
+
<ChatHeader>
|
|
68
|
+
<Typography variant="h6" component="h2">
|
|
69
|
+
Chat
|
|
70
|
+
</Typography>
|
|
71
|
+
{isLoading && <CircularProgress size={16} />}
|
|
72
|
+
</ChatHeader>
|
|
73
|
+
<ChatMessageList messages={messages} />
|
|
74
|
+
<ChatInput onSubmit={handleSubmit} disabled={isLoading} />
|
|
75
|
+
</ChatContainer>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { ChatMessage as ChatMessageType } from 'types'
|
|
9
|
+
|
|
10
|
+
type Props = { messages: ChatMessageType[] }
|
|
11
|
+
|
|
12
|
+
const MessageListContainer = styled(Box)`
|
|
13
|
+
flex: 1;
|
|
14
|
+
overflow-y: auto;
|
|
15
|
+
padding: ${({ theme }) => (theme as Theme).spacing(2)};
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: ${({ theme }) => (theme as Theme).spacing(1.5)};
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
export function ChatMessageList({ messages }: Props) {
|
|
22
|
+
const bottomRef = useRef<HTMLDivElement>(null)
|
|
23
|
+
const lastMessage = messages[messages.length - 1]
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
27
|
+
}, [messages.length, lastMessage?.body])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<MessageListContainer>
|
|
31
|
+
{messages.map((m) => (
|
|
32
|
+
<ChatMessage key={m.id} message={m} />
|
|
33
|
+
))}
|
|
34
|
+
<div ref={bottomRef} />
|
|
35
|
+
</MessageListContainer>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import { AppLogo } from '@atoms'
|
|
3
|
+
import { Box, Typography, Button } from '@mui/material'
|
|
4
|
+
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
|
|
5
|
+
import Link from 'next/link'
|
|
6
|
+
|
|
7
|
+
interface AppPageLayoutProps {
|
|
8
|
+
title: string
|
|
9
|
+
children: ReactNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function PageLayout({ title, children }: AppPageLayoutProps) {
|
|
13
|
+
return (
|
|
14
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh', bgcolor: 'background.default' }}>
|
|
15
|
+
<Box
|
|
16
|
+
component="header"
|
|
17
|
+
sx={{
|
|
18
|
+
p: 2,
|
|
19
|
+
px: 3,
|
|
20
|
+
display: 'flex',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
gap: 2,
|
|
23
|
+
borderBottom: 1,
|
|
24
|
+
borderColor: 'divider',
|
|
25
|
+
bgcolor: 'background.paper',
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<AppLogo width={32} height={32} />
|
|
29
|
+
<Typography variant="h6" fontWeight="bold" sx={{ flexGrow: 1 }}>
|
|
30
|
+
{title}
|
|
31
|
+
</Typography>
|
|
32
|
+
<Button component={Link} href="/" variant="text" startIcon={<ArrowBackIcon />} sx={{ color: 'text.secondary' }}>
|
|
33
|
+
Back to Home
|
|
34
|
+
</Button>
|
|
35
|
+
</Box>
|
|
36
|
+
<Box sx={{ flex: 1, p: 3, display: 'flex', justifyContent: 'center', overflow: 'hidden' }}>
|
|
37
|
+
<Box sx={{ width: '100%', maxWidth: 'md', height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
38
|
+
{children}
|
|
39
|
+
</Box>
|
|
40
|
+
</Box>
|
|
41
|
+
</Box>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useRef } from 'react'
|
|
4
|
+
import { Typography } from '@mui/material'
|
|
5
|
+
import { ChatMessageList } from './ChatMessageList'
|
|
6
|
+
import { ChatContainer, ChatHeader } from '@atoms'
|
|
7
|
+
import { ChatInput } from '@molecules'
|
|
8
|
+
import type { ChatMessage as ChatMessageType } from 'types'
|
|
9
|
+
import { EventType } from '@ag-ui/client'
|
|
10
|
+
|
|
11
|
+
const SESSION_ID = 'demo-stream-session'
|
|
12
|
+
|
|
13
|
+
type StreamEvent = {
|
|
14
|
+
type: EventType
|
|
15
|
+
delta?: string
|
|
16
|
+
agentId?: string
|
|
17
|
+
result?: Record<string, unknown>
|
|
18
|
+
message?: string
|
|
19
|
+
content?: string
|
|
20
|
+
name?: string
|
|
21
|
+
value?: Record<string, unknown>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function StreamChat() {
|
|
25
|
+
const [messages, setMessages] = useState<ChatMessageType[]>([])
|
|
26
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
27
|
+
const [isTransitioning, setIsTransitioning] = useState(false)
|
|
28
|
+
const assistantIdRef = useRef<string>('')
|
|
29
|
+
|
|
30
|
+
const handleSubmit = useCallback(async (text: string) => {
|
|
31
|
+
const userMsg: ChatMessageType = {
|
|
32
|
+
id: crypto.randomUUID(),
|
|
33
|
+
body: text,
|
|
34
|
+
source: 'user',
|
|
35
|
+
}
|
|
36
|
+
setMessages((prev) => [...prev, userMsg])
|
|
37
|
+
setIsLoading(true)
|
|
38
|
+
|
|
39
|
+
// Create a placeholder assistant message for streaming into
|
|
40
|
+
let assistantId = crypto.randomUUID()
|
|
41
|
+
assistantIdRef.current = assistantId
|
|
42
|
+
|
|
43
|
+
const streamingMsg: ChatMessageType = {
|
|
44
|
+
id: assistantId,
|
|
45
|
+
body: '',
|
|
46
|
+
source: 'assistant',
|
|
47
|
+
isStreaming: true,
|
|
48
|
+
}
|
|
49
|
+
setMessages((prev) => [...prev, streamingMsg])
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch('/api/stream', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify({ message: text, sessionId: SESSION_ID }),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(`HTTP error: ${response.status}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const reader = response.body?.getReader()
|
|
63
|
+
if (!reader) throw new Error('No response body')
|
|
64
|
+
|
|
65
|
+
const decoder = new TextDecoder()
|
|
66
|
+
let buffer = ''
|
|
67
|
+
|
|
68
|
+
while (true) {
|
|
69
|
+
const { done, value } = await reader.read()
|
|
70
|
+
if (done) break
|
|
71
|
+
|
|
72
|
+
buffer += decoder.decode(value, { stream: true })
|
|
73
|
+
|
|
74
|
+
// Process complete SSE lines
|
|
75
|
+
const lines = buffer.split('\n')
|
|
76
|
+
buffer = lines.pop() ?? ''
|
|
77
|
+
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (!line.startsWith('data: ')) continue
|
|
80
|
+
const data = line.slice(6)
|
|
81
|
+
if (data === '[DONE]') continue
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const event = JSON.parse(data) as StreamEvent
|
|
85
|
+
|
|
86
|
+
if (event.type === EventType.TEXT_MESSAGE_CONTENT && event.delta) {
|
|
87
|
+
setIsTransitioning(false)
|
|
88
|
+
setMessages((prev) => prev.map((m) => (m.id === assistantId ? { ...m, body: m.body + event.delta } : m)))
|
|
89
|
+
} else if (event.type === EventType.CUSTOM && event.name === 'AgentTransition') {
|
|
90
|
+
const prevAssistantId = assistantId
|
|
91
|
+
assistantId = crypto.randomUUID()
|
|
92
|
+
assistantIdRef.current = assistantId
|
|
93
|
+
setIsTransitioning(true)
|
|
94
|
+
setMessages((prev) => {
|
|
95
|
+
const updated = prev.map((m) => (m.id === prevAssistantId ? { ...m, isStreaming: false } : m))
|
|
96
|
+
return [...updated, { id: assistantId, body: '', source: 'assistant', isStreaming: true }]
|
|
97
|
+
})
|
|
98
|
+
} else if (event.type === EventType.RUN_FINISHED) {
|
|
99
|
+
setIsTransitioning(false)
|
|
100
|
+
setMessages((prev) => prev.map((m) => (m.id === assistantId ? { ...m, isStreaming: false } : m)))
|
|
101
|
+
} else if (event.type === EventType.RUN_ERROR) {
|
|
102
|
+
setIsTransitioning(false)
|
|
103
|
+
setMessages((prev) =>
|
|
104
|
+
prev.map((m) =>
|
|
105
|
+
m.id === assistantId
|
|
106
|
+
? { ...m, body: m.body || 'Sorry, something went wrong.', isStreaming: false }
|
|
107
|
+
: m,
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Skip malformed JSON lines
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Ensure streaming flag is cleared
|
|
118
|
+
setMessages((prev) => prev.map((m) => (m.id === assistantId ? { ...m, isStreaming: false } : m)))
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('Chat stream error:', error)
|
|
121
|
+
setMessages((prev) =>
|
|
122
|
+
prev.map((m) =>
|
|
123
|
+
m.id === assistantId
|
|
124
|
+
? { ...m, body: m.body || 'Sorry, something went wrong. Please try again.', isStreaming: false }
|
|
125
|
+
: m,
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
} finally {
|
|
129
|
+
setIsLoading(false)
|
|
130
|
+
}
|
|
131
|
+
}, [])
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<ChatContainer elevation={0}>
|
|
135
|
+
<ChatHeader>
|
|
136
|
+
<Typography variant="h6" component="h2">
|
|
137
|
+
Chat (Streaming)
|
|
138
|
+
</Typography>
|
|
139
|
+
</ChatHeader>
|
|
140
|
+
<ChatMessageList messages={messages} />
|
|
141
|
+
{isTransitioning && (
|
|
142
|
+
<Typography variant="caption" color="textSecondary" sx={{ px: 2, pb: 1, fontStyle: 'italic' }}>
|
|
143
|
+
Agent transition in progress...
|
|
144
|
+
</Typography>
|
|
145
|
+
)}
|
|
146
|
+
<ChatInput onSubmit={handleSubmit} disabled={isLoading} />
|
|
147
|
+
</ChatContainer>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
|
2
|
+
<defs>
|
|
3
|
+
<style>
|
|
4
|
+
@media (prefers-color-scheme: dark) {
|
|
5
|
+
.logo-mark { fill: #ffffff; }
|
|
6
|
+
}
|
|
7
|
+
</style>
|
|
8
|
+
</defs>
|
|
9
|
+
<g>
|
|
10
|
+
<path class="logo-mark" d="M 560.50 870.11 C544.60,872.39 493.52,873.54 477.50,871.98 C429.64,867.32 381.89,853.57 340.50,832.53 C316.53,820.34 296.91,807.54 274.00,789.15 C261.51,779.12 236.58,754.18 225.88,741.00 C180.72,685.39 153.06,618.33 146.08,547.58 C144.70,533.59 144.69,497.56 146.06,483.42 C151.82,424.12 172.58,365.85 205.33,317.00 C257.37,239.38 349.03,180.93 442.00,166.07 C464.04,162.55 478.67,161.69 505.88,162.29 C555.48,163.38 594.16,171.21 639.00,189.25 C659.38,197.45 694.34,215.75 702.79,222.64 L 705.07 224.50 L 699.18 234.00 C695.94,239.23 690.48,248.11 687.05,253.75 C683.61,259.39 680.44,264.00 679.99,264.00 C679.54,264.00 674.52,261.04 668.84,257.42 C655.11,248.69 625.53,234.22 610.00,228.66 C583.75,219.25 553.35,212.43 527.00,210.05 C512.11,208.70 478.67,208.71 464.00,210.07 C448.87,211.47 421.38,216.98 407.00,221.49 C359.48,236.39 317.14,262.02 281.44,297.50 C258.31,320.48 241.26,343.77 226.14,373.00 C202.42,418.87 191.69,463.62 191.74,516.50 C191.78,555.23 197.10,585.98 210.10,622.48 C215.21,636.83 229.38,665.53 238.40,679.79 C258.58,711.72 284.51,739.87 314.60,762.50 C358.47,795.49 416.85,818.66 475.00,826.15 C490.90,828.19 536.15,827.93 552.00,825.69 C589.08,820.47 619.66,811.35 651.80,795.94 C758.47,744.79 825.34,642.74 828.69,526.00 C830.16,474.91 818.90,425.90 794.39,376.67 C790.88,369.61 788.00,363.68 788.00,363.49 C788.00,362.88 830.91,349.00 832.81,349.00 C834.20,349.00 836.30,352.42 841.26,362.75 C849.43,379.78 850.04,381.23 856.16,398.49 C887.34,486.35 882.15,583.99 841.89,667.00 C819.83,712.49 793.79,746.96 757.00,779.42 C700.79,829.00 634.78,859.47 560.50,870.11 ZM 335.80 648.00 L 331.50 660.50 L 297.25 660.76 C278.41,660.91 263.00,660.77 263.00,660.45 C263.00,659.86 278.07,617.38 291.17,581.00 C295.04,570.28 308.24,533.38 320.52,499.00 C332.80,464.62 347.87,422.55 354.01,405.50 C360.15,388.45 366.62,370.45 368.39,365.50 L 371.61 356.50 L 409.10 356.24 L 446.59 355.98 L 457.22 385.74 C463.07,402.11 475.32,436.42 484.45,462.00 C493.58,487.58 502.36,512.10 503.97,516.50 C505.58,520.90 515.27,547.90 525.51,576.50 C535.75,605.10 546.52,635.12 549.43,643.22 C552.35,651.31 554.49,658.17 554.20,658.47 C553.91,658.76 537.97,659.00 518.78,659.00 L 483.90 659.00 L 482.58 655.75 C481.85,653.96 477.26,640.69 472.38,626.26 L 463.50 600.02 L 352.45 600.00 L 346.28 617.75 C342.88,627.51 338.17,641.12 335.80,648.00 ZM 687.50 661.35 C660.93,665.46 637.94,663.57 615.06,655.41 C601.27,650.49 591.03,644.01 580.37,633.46 C572.36,625.53 570.61,623.14 566.13,613.96 C560.20,601.81 557.14,589.54 556.76,576.32 L 556.50 567.50 L 616.57 566.97 L 617.62 574.74 C620.91,598.97 640.94,614.52 666.50,612.68 C689.18,611.04 704.64,597.72 707.93,576.97 C709.92,564.44 706.02,552.13 697.07,542.67 C687.39,532.43 676.26,529.04 652.23,529.02 L 636.96 529.00 L 637.23 504.75 L 637.50 480.50 L 654.00 479.93 C674.62,479.21 680.48,477.31 690.26,468.16 C697.55,461.34 700.26,455.37 700.77,445.00 C701.35,433.25 698.88,426.59 691.06,418.89 C682.94,410.89 676.70,408.61 663.00,408.61 C649.61,408.61 643.48,410.81 635.00,418.66 C628.14,425.01 625.48,428.98 622.42,437.42 L 620.22 443.50 L 591.93 443.77 C576.36,443.91 563.30,443.69 562.90,443.27 C561.92,442.23 564.65,426.97 567.16,419.51 C578.07,387.00 606.35,365.08 645.92,358.47 C659.23,356.24 682.31,356.90 695.19,359.88 C714.30,364.30 729.13,372.16 741.60,384.49 C755.80,398.54 761.90,412.81 762.75,434.04 C763.17,444.62 762.92,447.89 761.13,455.04 C756.76,472.50 747.16,486.30 733.31,495.04 C729.72,497.32 727.16,499.52 727.64,499.94 C728.11,500.37 731.08,501.76 734.24,503.03 C747.92,508.57 761.40,522.70 767.11,537.47 C771.91,549.87 773.33,559.36 772.69,574.50 C772.04,589.80 770.30,597.27 764.55,609.50 C751.70,636.85 723.57,655.78 687.50,661.35 ZM 370.99 545.75 C371.00,546.73 379.19,547.00 408.61,547.00 C438.28,547.00 446.12,546.74 445.75,545.75 C445.49,545.06 437.02,519.64 426.93,489.25 C416.83,458.86 408.31,434.00 407.99,434.00 C407.33,434.00 370.98,543.79 370.99,545.75 Z" fill="rgb(4,4,4)"/>
|
|
11
|
+
<path d="M 806.90 322.97 C785.45,328.12 765.24,322.48 749.97,307.09 C737.88,294.91 733.00,282.92 733.00,265.40 C733.00,254.57 734.84,247.01 739.72,237.71 C746.39,225.02 759.41,213.62 772.68,208.85 C780.93,205.88 799.11,205.17 807.92,207.47 C830.44,213.34 847.16,231.12 851.67,253.99 C855.45,273.14 849.31,292.70 835.02,307.04 C826.17,315.92 818.73,320.14 806.90,322.97 Z" fill="rgb(248,175,81)"/>
|
|
12
|
+
</g>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { ThemeProvider } from './ThemeProvider'
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: 'A3 Core Example',
|
|
6
|
+
description: 'Example application for @genui-a3/core',
|
|
7
|
+
icons: [
|
|
8
|
+
{ rel: 'icon', url: '/favicon.ico', type: 'image/x-icon', sizes: '32x32', media: '(prefers-color-scheme: light)' },
|
|
9
|
+
{
|
|
10
|
+
rel: 'icon',
|
|
11
|
+
url: '/favicon-dark.ico',
|
|
12
|
+
type: 'image/x-icon',
|
|
13
|
+
sizes: '32x32',
|
|
14
|
+
media: '(prefers-color-scheme: dark)',
|
|
15
|
+
},
|
|
16
|
+
{ rel: 'icon', url: '/icon.svg', type: 'image/svg+xml', media: '(prefers-color-scheme: light)' },
|
|
17
|
+
{ rel: 'icon', url: '/icon.svg', type: 'image/svg+xml', media: '(prefers-color-scheme: dark)' },
|
|
18
|
+
{ rel: 'apple-touch-icon', url: '/apple-icon.png', media: '(prefers-color-scheme: light)' },
|
|
19
|
+
{ rel: 'apple-touch-icon', url: '/apple-icon-dark.png', media: '(prefers-color-scheme: dark)' },
|
|
20
|
+
],
|
|
21
|
+
manifest: '/site.webmanifest',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
25
|
+
return (
|
|
26
|
+
<html lang="en">
|
|
27
|
+
<body>
|
|
28
|
+
<ThemeProvider>{children}</ThemeProvider>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
31
|
+
)
|
|
32
|
+
}
|