用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill building-chat-widgets命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
基于 SOC 职业分类
正在显示 SKILL.md
| name | building-chat-widgets |
| description | | Use when this capability is needed. |
Create interactive widgets for AI chat with actions and entity tagging.
const chatkit = useChatKit({
api: { url: API_URL, domainKey: DOMAIN_KEY },
widgets: {
onAction: async (action, widgetItem) => {
if (action.type === "view_details") {
navigate(`/details/${action.payload.id}`);
}
},
},
});
| Handler | Defined In | Processed By | Use Case |
|---|---|---|---|
"client" | Widget template | Frontend onAction | Navigation, local state |
"server" | Widget template | Backend action() | Data mutation, widget replacement |
1. Agent tool generates widget → yield WidgetItem
2. Widget renders in chat with action buttons
3. User clicks action → action dispatched
4. Handler processes action:
- client: onAction callback in frontend
- server: action() method in ChatKitServer
5. Optional: Widget replaced with updated state
Define reusable widget layouts with dynamic data:
{
"type": "ListView",
"children": [
{
"type": "ListViewItem",
"key": "item-1",
"onClickAction": {
"type": "item.select",
"handler": "client",
"payload": { "itemId": "item-1" }
},
"children": [
{
"type": "Row",
"gap": 3,
"children": [
{ "type": "Icon", "name"
Actions that update local state, navigate, or send follow-up messages:
Widget Definition:
{
"type": "Button",
"label": "View Article",
"onClickAction": {
"type": "open_article",
"handler": "client",
"payload": { "id": "article-123" }
}
}
Frontend Handler:
const chatkit = useChatKit({
api: { url: API_URL, domainKey: DOMAIN_KEY },
widgets: {
onAction: async (action, widgetItem) => {
switch (action.type) {
case "open_article":
navigate(`/article/${action.payload?.id}`);
break;
case "more_suggestions":
await chatkit.sendUserMessage({ text: "More suggestions, please" });
break;
case "select_option":
setSelectedOption(action.payload?.optionId);
break;
}
},
},
});
Actions that mutate data, update widgets, or require backend processing:
Widget Definition:
{
"type": "ListViewItem",
"onClickAction": {
"type": "line.select",
"handler": "server",
"payload": { "id": "blue-line" }
}
}
Backend Handler:
from chatkit.types import (
Action, WidgetItem, ThreadItemReplacedEvent,
ThreadItemDoneEvent, AssistantMessageItem, ClientEffectEvent,
)
class MyServer(ChatKitServer[dict]):
async def action(
self,
thread: ThreadMetadata,
action: Action[str, Any],
sender: WidgetItem | None,
context: RequestContext, # Note: Already RequestContext, not dict
) -> AsyncIterator[ThreadStreamEvent]:
if action.type == "line.select":
line_id = action.payload["id"] # Use .payload, not .arguments
# 1. Update widget with selection
updated_widget = build_selector_widget(selected=line_id)
yield ThreadItemReplacedEvent(
item=sender.model_copy(update={"widget": updated_widget})
)
# 2. Stream assistant message
yield ThreadItemDoneEvent(
item=AssistantMessageItem(
id=self.store.generate_item_id("msg", thread, context),
thread_id=thread.id,
created_at=datetime.now(),
content=[{"text": f"Selected {line_id}"}],
)
)
# 3. Trigger client effect
yield ClientEffectEvent(
name="selection_changed",
data={"lineId": line_id},
)
Allow users to @mention entities in messages:
const chatkit = useChatKit({
api: { url: API_URL, domainKey: DOMAIN_KEY },
entities: {
onTagSearch: async (query: string): Promise<Entity[]> => {
const results = await fetch(`/api/search?q=${query}`).then(r => r.json());
return results.map((item) => ({
id: item.id,
title: item.name,
icon: item.type === "person" ? "profile" : "document",
group: item.type === "People" ? "People" : "Articles",
interactive: true,
data: { type: item.type, article_id: item.id },
}));
},
onClick: (entity: Entity) => {
(entity.?.) {
();
}
},
},
});
Let users select different AI modes from the composer:
const TOOL_CHOICES = [
{
id: "general",
label: "Chat",
icon: "sparkle",
placeholderOverride: "Ask anything...",
pinned: true,
},
{
id: "event_finder",
label: "Find Events",
icon: "calendar",
placeholderOverride: "What events are you looking for?",
pinned: true,
},
];
const chatkit = useChatKit({
api: { url: API_URL, domainKey: DOMAIN_KEY },
composer: {
placeholder: "What would you like to do?",
tools: TOOL_CHOICES,
},
});
Backend Routing:
async def respond(self, thread, item, context):
tool_choice = context.metadata.get("tool_choice")
if tool_choice == "event_finder":
agent = self.event_finder_agent
else:
agent = self.general_agent
result = Runner.run_streamed(agent, input_items)
async for event in stream_agent_response(context, result):
yield event
| Component | Props | Description |
|---|---|---|
ListView | children | Scrollable list container |
ListViewItem | key, onClickAction, children | Clickable list item |
Row | gap, align, justify, children | Horizontal flex |
Col | gap, padding, children | Vertical flex |
Box | size, radius, background, padding | Styled container |
| Component | Props | Description |
|---|---|---|
Text | value, size, weight, color | Text display |
Title | value, size, weight | Heading text |
Image | src, alt, width, height | Image display |
Icon | name, size, color | Icon from set |
| Component | Props | Description |
|---|---|---|
Button | label, variant, onClickAction | Clickable button |
IMPORTANT: Use action.payload, NOT action.arguments:
# WRONG - Will cause AttributeError
action.arguments
# CORRECT
action.payload
The context parameter is RequestContext, not dict:
# WRONG - Tries to wrap RequestContext
request_context = RequestContext(metadata=context)
# CORRECT - Use directly
user_id = context.user_id
When creating synthetic user messages:
from chatkit.types import UserMessageItem, UserMessageTextContent
# Include ALL required fields
synthetic_message = UserMessageItem(
id=self.store.generate_item_id("message", thread, context),
thread_id=thread.id,
created_at=datetime.now(),
content=[UserMessageTextContent(type="input_text", text=message_text)],
inference_options={},
)
action.payloadtype="input_text" for user messagesRun: python3 scripts/verify.py
Expected: ✓ building-chat-widgets skill ready
Converted and distributed by TomeVault — claim your Tome and manage your conversions.