@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,56 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react'
|
|
4
|
+
import { Handle, Position, type NodeProps } from '@xyflow/react'
|
|
5
|
+
import { Box, Typography } from '@mui/material'
|
|
6
|
+
import { GRAPH_ACTIVE_BADGE } from '@constants/ui'
|
|
7
|
+
|
|
8
|
+
function AgentNodeComponent({ data }: NodeProps) {
|
|
9
|
+
const { label, isActive } = data as { label: string; isActive: boolean; description: string }
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<Handle type="target" id="target-top" position={Position.Top} style={{ opacity: 0 }} />
|
|
14
|
+
<Handle type="target" id="target-right" position={Position.Right} style={{ opacity: 0 }} />
|
|
15
|
+
<Box
|
|
16
|
+
sx={{
|
|
17
|
+
px: 2,
|
|
18
|
+
py: 1,
|
|
19
|
+
borderRadius: '10px',
|
|
20
|
+
bgcolor: isActive ? '#2563eb' : '#e2e8f0',
|
|
21
|
+
border: '1.5px solid',
|
|
22
|
+
borderColor: isActive ? '#1d4ed8' : '#cbd5e1',
|
|
23
|
+
boxShadow: isActive ? '0 0 8px rgba(37, 99, 235, 0.5)' : 'none',
|
|
24
|
+
animation: isActive ? 'agentPulse 2s ease-in-out infinite' : 'none',
|
|
25
|
+
'@keyframes agentPulse': {
|
|
26
|
+
'0%, 100%': { boxShadow: '0 0 4px rgba(37, 99, 235, 0.3)' },
|
|
27
|
+
'50%': { boxShadow: '0 0 12px rgba(37, 99, 235, 0.6)' },
|
|
28
|
+
},
|
|
29
|
+
minWidth: 100,
|
|
30
|
+
textAlign: 'center',
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<Typography
|
|
34
|
+
sx={{
|
|
35
|
+
fontSize: 12,
|
|
36
|
+
fontWeight: isActive ? 700 : 500,
|
|
37
|
+
color: isActive ? '#ffffff' : '#1e293b',
|
|
38
|
+
lineHeight: 1.2,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{label}
|
|
42
|
+
</Typography>
|
|
43
|
+
</Box>
|
|
44
|
+
{isActive && (
|
|
45
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, mt: 0.5 }}>
|
|
46
|
+
<Box sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: '#22c55e' }} />
|
|
47
|
+
<Typography sx={{ fontSize: 9, fontWeight: 600, color: '#2563eb' }}>{GRAPH_ACTIVE_BADGE}</Typography>
|
|
48
|
+
</Box>
|
|
49
|
+
)}
|
|
50
|
+
<Handle type="source" id="source-bottom" position={Position.Bottom} style={{ opacity: 0 }} />
|
|
51
|
+
<Handle type="source" id="source-right" position={Position.Right} style={{ opacity: 0 }} />
|
|
52
|
+
</>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const AgentNode = memo(AgentNodeComponent)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const MARK_PATH =
|
|
2
|
+
'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'
|
|
3
|
+
const DOT_PATH =
|
|
4
|
+
'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'
|
|
5
|
+
|
|
6
|
+
export type AppLogoProps = {
|
|
7
|
+
/** Fill color for the A3 + ring (default: dark gray) */
|
|
8
|
+
markColor?: string
|
|
9
|
+
/** Fill color for the accent dot (default: orange) */
|
|
10
|
+
dotColor?: string
|
|
11
|
+
width?: number | string
|
|
12
|
+
height?: number | string
|
|
13
|
+
className?: string
|
|
14
|
+
'aria-label'?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_MARK = 'rgb(4,4,4)'
|
|
18
|
+
const DEFAULT_DOT = 'rgb(248,175,81)'
|
|
19
|
+
|
|
20
|
+
export function AppLogo({
|
|
21
|
+
markColor = DEFAULT_MARK,
|
|
22
|
+
dotColor = DEFAULT_DOT,
|
|
23
|
+
width = 32,
|
|
24
|
+
height = 32,
|
|
25
|
+
className,
|
|
26
|
+
'aria-label': ariaLabel = 'A3',
|
|
27
|
+
}: AppLogoProps) {
|
|
28
|
+
return (
|
|
29
|
+
<svg
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
31
|
+
viewBox="0 0 1024 1024"
|
|
32
|
+
width={width}
|
|
33
|
+
height={height}
|
|
34
|
+
className={className}
|
|
35
|
+
aria-label={ariaLabel}
|
|
36
|
+
role="img"
|
|
37
|
+
>
|
|
38
|
+
<g>
|
|
39
|
+
<path d={MARK_PATH} fill={markColor} />
|
|
40
|
+
<path d={DOT_PATH} fill={dotColor} />
|
|
41
|
+
</g>
|
|
42
|
+
</svg>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -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,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
import { IconButton, Tooltip, CircularProgress } from '@mui/material'
|
|
6
|
+
import RestartAltIcon from '@mui/icons-material/RestartAlt'
|
|
7
|
+
import type { Theme } from '@mui/material/styles'
|
|
8
|
+
import { CHAT_RESTART } from '@constants/ui'
|
|
9
|
+
|
|
10
|
+
const HeaderContainer = styled.div`
|
|
11
|
+
border-bottom: 1px solid ${({ theme }) => (theme as Theme).palette.divider};
|
|
12
|
+
background-color: ${({ theme }) => (theme as Theme).palette.background.paper};
|
|
13
|
+
padding: ${({ theme }) => (theme as Theme).spacing(2, 3)};
|
|
14
|
+
flex-shrink: 0;
|
|
15
|
+
display: flex;
|
|
16
|
+
justify-content: flex-end;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: ${({ theme }) => (theme as Theme).spacing(1)};
|
|
19
|
+
min-height: 40px;
|
|
20
|
+
height: 40px;
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
interface ChatHeaderProps {
|
|
24
|
+
children?: ReactNode
|
|
25
|
+
onRestart?: () => void
|
|
26
|
+
isRestarting?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ChatHeader({ children, onRestart, isRestarting }: ChatHeaderProps) {
|
|
30
|
+
return (
|
|
31
|
+
<HeaderContainer>
|
|
32
|
+
{children}
|
|
33
|
+
{onRestart && (
|
|
34
|
+
<Tooltip title={CHAT_RESTART}>
|
|
35
|
+
<span>
|
|
36
|
+
<IconButton
|
|
37
|
+
aria-label={CHAT_RESTART}
|
|
38
|
+
onClick={onRestart}
|
|
39
|
+
disabled={isRestarting}
|
|
40
|
+
size="small"
|
|
41
|
+
>
|
|
42
|
+
{isRestarting ? <CircularProgress size={16} /> : <RestartAltIcon fontSize="small" />}
|
|
43
|
+
</IconButton>
|
|
44
|
+
</span>
|
|
45
|
+
</Tooltip>
|
|
46
|
+
)}
|
|
47
|
+
</HeaderContainer>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { styled, useTheme } from '@mui/material/styles'
|
|
4
|
+
import Typography from '@mui/material/Typography'
|
|
5
|
+
import Link from '@mui/material/Link'
|
|
6
|
+
import ReactMarkdown, { type Components } from 'react-markdown'
|
|
7
|
+
import remarkGfm from 'remark-gfm'
|
|
8
|
+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|
9
|
+
import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
|
10
|
+
import { REPO_URL } from '@constants/paths'
|
|
11
|
+
|
|
12
|
+
const MarkdownRoot = styled('div')(({ theme }) => ({
|
|
13
|
+
'& > *:first-of-type': { marginTop: 0 },
|
|
14
|
+
'& > *:last-child': { marginBottom: 0 },
|
|
15
|
+
'& ul, & ol': {
|
|
16
|
+
paddingLeft: theme.spacing(3),
|
|
17
|
+
marginTop: theme.spacing(0.5),
|
|
18
|
+
marginBottom: theme.spacing(0.5),
|
|
19
|
+
},
|
|
20
|
+
'& li': {
|
|
21
|
+
marginBottom: theme.spacing(0.25),
|
|
22
|
+
},
|
|
23
|
+
'& blockquote': {
|
|
24
|
+
margin: theme.spacing(1, 0),
|
|
25
|
+
padding: theme.spacing(0.5, 2),
|
|
26
|
+
borderLeft: `4px solid ${theme.palette.primary.main}`,
|
|
27
|
+
backgroundColor: theme.palette.action.hover,
|
|
28
|
+
},
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
function useMarkdownComponents(): Components {
|
|
32
|
+
const theme = useTheme()
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
p: ({ children }) => (
|
|
36
|
+
<Typography variant="body2" sx={{ my: 0.5 }}>
|
|
37
|
+
{children}
|
|
38
|
+
</Typography>
|
|
39
|
+
),
|
|
40
|
+
h1: ({ children }) => (
|
|
41
|
+
<Typography variant="h5" sx={{ mt: 2, mb: 1, fontWeight: 600 }}>
|
|
42
|
+
{children}
|
|
43
|
+
</Typography>
|
|
44
|
+
),
|
|
45
|
+
h2: ({ children }) => (
|
|
46
|
+
<Typography variant="h6" sx={{ mt: 1.5, mb: 0.75, fontWeight: 600 }}>
|
|
47
|
+
{children}
|
|
48
|
+
</Typography>
|
|
49
|
+
),
|
|
50
|
+
h3: ({ children }) => (
|
|
51
|
+
<Typography variant="subtitle1" sx={{ mt: 1.5, mb: 0.5, fontWeight: 600 }}>
|
|
52
|
+
{children}
|
|
53
|
+
</Typography>
|
|
54
|
+
),
|
|
55
|
+
h4: ({ children }) => (
|
|
56
|
+
<Typography variant="subtitle2" sx={{ mt: 1, mb: 0.5, fontWeight: 600 }}>
|
|
57
|
+
{children}
|
|
58
|
+
</Typography>
|
|
59
|
+
),
|
|
60
|
+
h5: ({ children }) => (
|
|
61
|
+
<Typography variant="body1" sx={{ mt: 1, mb: 0.5, fontWeight: 600 }}>
|
|
62
|
+
{children}
|
|
63
|
+
</Typography>
|
|
64
|
+
),
|
|
65
|
+
h6: ({ children }) => (
|
|
66
|
+
<Typography variant="body2" sx={{ mt: 1, mb: 0.5, fontWeight: 600 }}>
|
|
67
|
+
{children}
|
|
68
|
+
</Typography>
|
|
69
|
+
),
|
|
70
|
+
a: ({ href, children }) => {
|
|
71
|
+
const resolvedHref = href?.match(/^\.?\/?docs\/.*\.md$/) ? `${REPO_URL}/${href.replace(/^\.\//, '')}` : href
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Link href={resolvedHref} target="_blank" rel="noopener noreferrer">
|
|
75
|
+
{children}
|
|
76
|
+
</Link>
|
|
77
|
+
)
|
|
78
|
+
},
|
|
79
|
+
li: ({ children }) => (
|
|
80
|
+
<Typography variant="body2" component="li" sx={{ my: 0.25 }}>
|
|
81
|
+
{children}
|
|
82
|
+
</Typography>
|
|
83
|
+
),
|
|
84
|
+
code: ({ className, children, ...props }) => {
|
|
85
|
+
const match = /language-(\w+)/.exec(className || '')
|
|
86
|
+
const codeString = String(children as string).replace(/\n$/, '')
|
|
87
|
+
const isBlock = !!match || codeString.includes('\n')
|
|
88
|
+
|
|
89
|
+
if (isBlock) {
|
|
90
|
+
return (
|
|
91
|
+
<SyntaxHighlighter
|
|
92
|
+
style={oneLight}
|
|
93
|
+
language={match?.[1] || 'text'}
|
|
94
|
+
PreTag="div"
|
|
95
|
+
customStyle={{
|
|
96
|
+
margin: theme.spacing(1, 0),
|
|
97
|
+
borderRadius: theme.shape.borderRadius,
|
|
98
|
+
fontSize: '0.8125rem',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{codeString}
|
|
102
|
+
</SyntaxHighlighter>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<code
|
|
108
|
+
style={{
|
|
109
|
+
backgroundColor: theme.palette.action.hover,
|
|
110
|
+
padding: '2px 6px',
|
|
111
|
+
borderRadius: theme.shape.borderRadius,
|
|
112
|
+
fontSize: '0.8125rem',
|
|
113
|
+
fontFamily: 'monospace',
|
|
114
|
+
}}
|
|
115
|
+
{...props}
|
|
116
|
+
>
|
|
117
|
+
{children}
|
|
118
|
+
</code>
|
|
119
|
+
)
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function MarkdownRenderer({ content }: { content: string }) {
|
|
125
|
+
const components = useMarkdownComponents()
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<MarkdownRoot>
|
|
129
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
|
|
130
|
+
{content}
|
|
131
|
+
</ReactMarkdown>
|
|
132
|
+
</MarkdownRoot>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
@@ -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,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react'
|
|
4
|
+
import { BaseEdge, getBezierPath, type EdgeProps } from '@xyflow/react'
|
|
5
|
+
|
|
6
|
+
function DeterministicEdgeComponent(props: EdgeProps) {
|
|
7
|
+
const [edgePath] = getBezierPath(props)
|
|
8
|
+
return (
|
|
9
|
+
<BaseEdge
|
|
10
|
+
path={edgePath}
|
|
11
|
+
markerEnd={props.markerEnd}
|
|
12
|
+
style={{ stroke: '#64748b', strokeWidth: 1.5 }}
|
|
13
|
+
/>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function DynamicEdgeComponent(props: EdgeProps) {
|
|
18
|
+
const [edgePath] = getBezierPath(props)
|
|
19
|
+
return (
|
|
20
|
+
<BaseEdge
|
|
21
|
+
path={edgePath}
|
|
22
|
+
markerEnd={props.markerEnd}
|
|
23
|
+
style={{ stroke: '#94a3b8', strokeWidth: 1.5, strokeDasharray: '5,3' }}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function SelfLoopEdgeComponent({ targetX, targetY, markerEnd, data }: EdgeProps) {
|
|
29
|
+
const isDynamic = (data as Record<string, unknown>)?.isDynamic
|
|
30
|
+
const stroke = isDynamic ? '#94a3b8' : '#64748b'
|
|
31
|
+
|
|
32
|
+
// Small arc above the node's target handle (top center)
|
|
33
|
+
const d = `M ${targetX - 12} ${targetY} C ${targetX - 12} ${targetY - 25}, ${targetX + 12} ${targetY - 25}, ${targetX + 12} ${targetY}`
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<path
|
|
37
|
+
d={d}
|
|
38
|
+
fill="none"
|
|
39
|
+
stroke={stroke}
|
|
40
|
+
strokeWidth={1.5}
|
|
41
|
+
strokeDasharray={isDynamic ? '5,3' : undefined}
|
|
42
|
+
markerEnd={markerEnd}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const DeterministicEdge = memo(DeterministicEdgeComponent)
|
|
48
|
+
export const DynamicEdge = memo(DynamicEdgeComponent)
|
|
49
|
+
export const SelfLoopEdge = memo(SelfLoopEdgeComponent)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { AgentNode } from './AgentNode'
|
|
2
|
+
export { AppLogo } from './AppLogo'
|
|
3
|
+
export { MarkdownRenderer } from './MarkdownRenderer'
|
|
4
|
+
export { MessageBubble } from './MessageBubble'
|
|
5
|
+
export { ChatContainer } from './ChatContainer'
|
|
6
|
+
export { ChatHeader } from './ChatHeader'
|
|
7
|
+
export { DeterministicEdge, DynamicEdge, SelfLoopEdge } from './TransitionEdge'
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect, useRef } 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
|
+
import { CHAT_PLACEHOLDER, CHAT_SEND } from '@constants/ui'
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
onSubmit: (text: string) => void | Promise<void>
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
placeholder?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const InputContainer = styled(Box)`
|
|
17
|
+
border-top: 1px solid ${({ theme }) => (theme as Theme).palette.divider};
|
|
18
|
+
background-color: ${({ theme }) => (theme as Theme).palette.background.paper};
|
|
19
|
+
padding: ${({ theme }) => (theme as Theme).spacing(2)};
|
|
20
|
+
flex-shrink: 0;
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
const InputForm = styled.form`
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: ${({ theme }) => (theme as Theme).spacing(1.5)};
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
export function ChatInput({ onSubmit, disabled, placeholder = CHAT_PLACEHOLDER }: Props) {
|
|
29
|
+
const [value, setValue] = useState('')
|
|
30
|
+
const inputRef = useRef<HTMLTextAreaElement>(null)
|
|
31
|
+
const prevDisabled = useRef(disabled)
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
// When transitioning from disabled (true) back to enabled (false), restore focus
|
|
35
|
+
if (prevDisabled.current && !disabled) {
|
|
36
|
+
// Small timeout to ensure the clear-disabled DOM update finishes
|
|
37
|
+
const timer = setTimeout(() => inputRef.current?.focus(), 10)
|
|
38
|
+
return () => clearTimeout(timer)
|
|
39
|
+
}
|
|
40
|
+
prevDisabled.current = disabled
|
|
41
|
+
}, [disabled])
|
|
42
|
+
|
|
43
|
+
const handleSubmit = useCallback(
|
|
44
|
+
(e: React.FormEvent) => {
|
|
45
|
+
e.preventDefault()
|
|
46
|
+
const trimmed = value.trim()
|
|
47
|
+
if (!trimmed || disabled) return
|
|
48
|
+
void onSubmit(trimmed)
|
|
49
|
+
setValue('')
|
|
50
|
+
},
|
|
51
|
+
[value, disabled, onSubmit],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const handleKeyDown = useCallback(
|
|
55
|
+
(e: React.KeyboardEvent) => {
|
|
56
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
57
|
+
e.preventDefault()
|
|
58
|
+
handleSubmit(e)
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[handleSubmit],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<InputContainer>
|
|
66
|
+
<InputForm onSubmit={handleSubmit}>
|
|
67
|
+
<TextField
|
|
68
|
+
inputRef={inputRef}
|
|
69
|
+
fullWidth
|
|
70
|
+
multiline
|
|
71
|
+
maxRows={6}
|
|
72
|
+
value={value}
|
|
73
|
+
onChange={(e) => setValue(e.target.value)}
|
|
74
|
+
onKeyDown={handleKeyDown}
|
|
75
|
+
placeholder={placeholder}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
variant="outlined"
|
|
78
|
+
size="small"
|
|
79
|
+
autoComplete="off"
|
|
80
|
+
data-testid="chat-input"
|
|
81
|
+
/>
|
|
82
|
+
<Button
|
|
83
|
+
type="submit"
|
|
84
|
+
variant="contained"
|
|
85
|
+
disabled={disabled || !value.trim()}
|
|
86
|
+
startIcon={<SendIcon />}
|
|
87
|
+
data-testid="chat-send"
|
|
88
|
+
>
|
|
89
|
+
{CHAT_SEND}
|
|
90
|
+
</Button>
|
|
91
|
+
</InputForm>
|
|
92
|
+
</InputContainer>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import styled, { keyframes } from 'styled-components'
|
|
2
|
+
import { Typography } from '@mui/material'
|
|
3
|
+
import { MarkdownRenderer, MessageBubble } from '@atoms'
|
|
4
|
+
import { MessageSender } from '@genui/a3'
|
|
5
|
+
import type { Message } from '@genui/a3'
|
|
6
|
+
|
|
7
|
+
const MessageRow = styled.div<{ $isUser: boolean }>`
|
|
8
|
+
display: flex;
|
|
9
|
+
justify-content: ${({ $isUser }) => ($isUser ? 'flex-end' : 'flex-start')};
|
|
10
|
+
`
|
|
11
|
+
|
|
12
|
+
const blink = keyframes`
|
|
13
|
+
0%, 100% { opacity: 1; }
|
|
14
|
+
50% { opacity: 0; }
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
const StreamingCursor = styled.span`
|
|
18
|
+
display: inline-block;
|
|
19
|
+
width: 6px;
|
|
20
|
+
height: 14px;
|
|
21
|
+
margin-left: 2px;
|
|
22
|
+
background-color: currentColor;
|
|
23
|
+
vertical-align: text-bottom;
|
|
24
|
+
animation: ${blink} 0.8s step-end infinite;
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
export function ChatMessage({ message }: { message: Message }) {
|
|
28
|
+
const isUser = message?.metadata?.source === MessageSender.USER
|
|
29
|
+
return (
|
|
30
|
+
<MessageRow $isUser={isUser} data-testid="chat-message">
|
|
31
|
+
<MessageBubble $isUser={isUser} elevation={0}>
|
|
32
|
+
{isUser ? (
|
|
33
|
+
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
34
|
+
{message.text.trim()}
|
|
35
|
+
</Typography>
|
|
36
|
+
) : (
|
|
37
|
+
<>
|
|
38
|
+
<MarkdownRenderer content={message.text.trim()} />
|
|
39
|
+
{message.isStreaming && <StreamingCursor />}
|
|
40
|
+
</>
|
|
41
|
+
)}
|
|
42
|
+
</MessageBubble>
|
|
43
|
+
</MessageRow>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react'
|
|
4
|
+
import { ReactFlow, ReactFlowProvider } from '@xyflow/react'
|
|
5
|
+
import { Paper, Typography, Box, Stack } from '@mui/material'
|
|
6
|
+
import { AgentNode } from '@atoms/AgentNode'
|
|
7
|
+
import { DeterministicEdge, DynamicEdge, SelfLoopEdge } from '@atoms/TransitionEdge'
|
|
8
|
+
import { getGraphLayout } from '@lib/getGraphLayout'
|
|
9
|
+
import type { AgentInfo } from '@lib/getAgentGraphData'
|
|
10
|
+
import { GRAPH_HEADING, GRAPH_LEGEND_DETERMINISTIC, GRAPH_LEGEND_LLM } from '@constants/ui'
|
|
11
|
+
import '@xyflow/react/dist/style.css'
|
|
12
|
+
|
|
13
|
+
interface AgentGraphProps {
|
|
14
|
+
agents: AgentInfo[]
|
|
15
|
+
activeAgentId: string | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const nodeTypes = { agent: AgentNode }
|
|
19
|
+
const edgeTypes = {
|
|
20
|
+
deterministic: DeterministicEdge,
|
|
21
|
+
dynamic: DynamicEdge,
|
|
22
|
+
selfLoop: SelfLoopEdge,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function AgentGraph({ agents, activeAgentId }: AgentGraphProps) {
|
|
26
|
+
const { nodes, edges } = useMemo(() => getGraphLayout(agents, activeAgentId), [agents, activeAgentId])
|
|
27
|
+
|
|
28
|
+
if (agents.length === 0) return null
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Paper elevation={0} sx={{ p: 2, border: 1, borderColor: 'divider', borderRadius: 3 }}>
|
|
32
|
+
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={1}>
|
|
33
|
+
<Typography variant="subtitle2" fontWeight="bold">
|
|
34
|
+
{GRAPH_HEADING}
|
|
35
|
+
</Typography>
|
|
36
|
+
<Stack direction="row" gap={2} alignItems="center">
|
|
37
|
+
<Stack direction="row" gap={0.5} alignItems="center">
|
|
38
|
+
<Box sx={{ width: 20, height: 0, borderTop: '2px solid #64748b' }} />
|
|
39
|
+
<Typography variant="caption" color="text.secondary">
|
|
40
|
+
{GRAPH_LEGEND_DETERMINISTIC}
|
|
41
|
+
</Typography>
|
|
42
|
+
</Stack>
|
|
43
|
+
<Stack direction="row" gap={0.5} alignItems="center">
|
|
44
|
+
<Box sx={{ width: 20, height: 0, borderTop: '2px dashed #94a3b8' }} />
|
|
45
|
+
<Typography variant="caption" color="text.secondary">
|
|
46
|
+
{GRAPH_LEGEND_LLM}
|
|
47
|
+
</Typography>
|
|
48
|
+
</Stack>
|
|
49
|
+
</Stack>
|
|
50
|
+
</Stack>
|
|
51
|
+
<Box sx={{ height: 350 }}>
|
|
52
|
+
<ReactFlowProvider>
|
|
53
|
+
<ReactFlow
|
|
54
|
+
nodes={nodes}
|
|
55
|
+
edges={edges}
|
|
56
|
+
nodeTypes={nodeTypes}
|
|
57
|
+
edgeTypes={edgeTypes}
|
|
58
|
+
fitView
|
|
59
|
+
fitViewOptions={{ padding: 0.4 }}
|
|
60
|
+
nodesDraggable={false}
|
|
61
|
+
nodesConnectable={false}
|
|
62
|
+
elementsSelectable={false}
|
|
63
|
+
zoomOnScroll={false}
|
|
64
|
+
zoomOnPinch={false}
|
|
65
|
+
zoomOnDoubleClick={false}
|
|
66
|
+
panOnDrag={false}
|
|
67
|
+
panOnScroll={false}
|
|
68
|
+
preventScrolling={false}
|
|
69
|
+
proOptions={{ hideAttribution: true }}
|
|
70
|
+
/>
|
|
71
|
+
</ReactFlowProvider>
|
|
72
|
+
</Box>
|
|
73
|
+
</Paper>
|
|
74
|
+
)
|
|
75
|
+
}
|