Claude-skill-registry castella-agent-ui
Build chat interfaces and agent management UIs with Castella. Create chat components, display tool calls, manage multiple agents, and build agent hubs.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/castella-agent-ui" ~/.claude/skills/majiayu000-claude-skill-registry-castella-agent-ui && rm -rf "$T"
skills/data/castella-agent-ui/SKILL.mdCastella Agent UI Components
High-level components for building conversational interfaces and agent management UIs.
When to use: "create a chat UI", "AgentChat", "chat with agent", "display tool calls", "multi-agent chat", "AgentHub", "message history", "MultiAgentChat"
Quick Start (3 Lines)
Create a chat UI connected to an A2A agent:
from castella.agent import AgentChat chat = AgentChat.from_a2a("http://localhost:8080") chat.run()
Installation
uv sync --extra agent # Agent UI + A2A + A2UI support
AgentChat
High-level chat component with minimal setup:
from castella.agent import AgentChat # Connect to A2A agent chat = AgentChat.from_a2a("http://localhost:8080") chat.run() # Or use custom handler function chat = AgentChat( handler=lambda msg: f"Echo: {msg}", title="Echo Bot", system_message="Welcome! How can I help?", ) chat.run()
Parameters
AgentChat( a2a_client=None, # A2AClient instance handler=None, # Custom handler: (str) -> str title="Agent Chat", # Window title placeholder="Type...", # Input placeholder system_message=None, # Initial system message show_agent_card=True, # Show agent card for A2A width=700, # Window width height=550, # Window height )
Factory Methods
# From A2A agent URL chat = AgentChat.from_a2a("http://localhost:8080") # From A2A client from castella.a2a import A2AClient client = A2AClient("http://localhost:8080") chat = AgentChat.from_a2a(client)
Chat Components
Build custom chat UIs with lower-level components.
ChatContainer
Complete chat UI (messages + input):
from castella.agent import ChatContainer, ChatMessageData from castella.core import ListState messages = ListState([]) def on_send(text: str): messages.append(ChatMessageData(role="user", content=text)) # Get response from agent... response = get_response(text) messages.append(ChatMessageData(role="assistant", content=response)) container = ChatContainer( messages, on_send=on_send, title="My Chat", placeholder="Type a message...", )
ChatMessage
Display a single message:
from castella.agent import ChatMessage, ChatMessageData msg = ChatMessageData( role="assistant", # "user", "assistant", or "system" content="Hello! How can I help you today?", ) widget = ChatMessage(msg)
ChatInput
Text input with send button:
from castella.agent import ChatInput input_widget = ChatInput( placeholder="Type a message...", on_send=lambda text: print(f"Sent: {text}"), )
ChatView
Scrollable message list:
from castella.agent import ChatView from castella.core import ScrollState scroll_state = ScrollState() view = ChatView(messages, scroll_state=scroll_state)
ChatMessageData
Message data structure:
from castella.agent import ChatMessageData, ToolCallData msg = ChatMessageData( role="assistant", content="Let me check the weather for you.", tool_calls=[ ToolCallData( id="call_1", name="get_weather", arguments={"location": "Tokyo"}, result="Sunny, 22°C", ) ], )
Tool Call Visualization
ToolCallView
Display a single tool call:
from castella.agent import ToolCallView tool = ToolCallView( name="get_weather", arguments={"location": "Tokyo"}, result="Sunny, 22°C", )
ToolHistoryPanel
Display history of tool calls:
from castella.agent import ToolHistoryPanel from castella.core import ListState tool_calls = ListState([...]) panel = ToolHistoryPanel(tool_calls)
Agent Card Display
AgentCardView
Show agent information:
from castella.agent import AgentCardView from castella.a2a import A2AClient client = A2AClient("http://agent.example.com") card_view = AgentCardView( client.agent_card, show_skills=True, compact=False, )
AgentListView
Display multiple agents:
from castella.agent import AgentListView agent_list = AgentListView( agents=[client1.agent_card, client2.agent_card], on_select=lambda card: print(f"Selected: {card.name}"), )
MultiAgentChat
Tabbed interface for multiple agents:
from castella.agent import MultiAgentChat from castella.a2a import A2AClient chat = MultiAgentChat({ "weather": A2AClient("http://localhost:8081"), "travel": A2AClient("http://localhost:8082"), "restaurant": A2AClient("http://localhost:8083"), }) chat.run()
Each agent gets its own chat tab with independent message history.
AgentHub
Agent discovery and management dashboard:
from castella.agent import AgentHub from castella.a2a import A2AClient # Create hub hub = AgentHub(title="Agent Hub") # Add agents hub.add_agent("http://localhost:8081") hub.add_agent(A2AClient("http://localhost:8082")) hub.run()
Or initialize with agents:
hub = AgentHub(agents=[ A2AClient("http://agent1.example.com"), A2AClient("http://agent2.example.com"), ])
Features:
- Left panel: List of agents with add/remove
- Right panel: Chat with selected agent
- URL input to add new agents at runtime
Scroll Position Pattern
Important pattern for chat UIs - set scroll before adding message:
class ChatComponent(Component): def __init__(self): super().__init__() self._messages = ListState([]) self._messages.attach(self) self._scroll_state = ScrollState() # DON'T attach scroll state def _send_message(self, text: str): # Add user message self._messages.append(ChatMessageData(role="user", content=text)) # Get response... response = get_response(text) # Set scroll BEFORE adding response (so re-render picks it up) self._scroll_state.y = 999999 self._messages.append(ChatMessageData(role="assistant", content=response))
Lazy State Attachment
For components created before App exists (like AgentHub):
class MyComponent(Component): def __init__(self): super().__init__() self._state = State(0) self._states_attached = False # DON'T attach here - App may not exist yet def view(self): # Attach lazily when view() is called if not self._states_attached: self._state.attach(self) self._states_attached = True return Text(str(self._state()))
Message Markdown Support
Messages support Markdown formatting:
msg = ChatMessageData( role="assistant", content=""" # Weather Report **Tokyo**: Sunny, 22°C | Day | High | Low | |-----|------|-----| | Mon | 24°C | 18°C | | Tue | 22°C | 17°C | """, )
Best Practices
- Use ScrollState without attaching for chat scroll
- Set scroll position before adding new messages
- Use Markdown for rich message content
- Handle loading states for async responses
- Use ListState.append() for new messages
- Use lazy state attachment for hub-style components
Reference
- Component API referencereferences/components.md
- ChatMessageData, ToolCallData typesreferences/data_classes.md
- Executable examples (simple_chat.py, multi_agent.py, agent_hub.py)scripts/