mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
feat(a2ui): Implement A2UI backend with question handling and WebSocket support
- Added A2UITypes for defining question structures and answers. - Created A2UIWebSocketHandler for managing WebSocket connections and message handling. - Developed ask-question tool for interactive user questions via A2UI. - Introduced platformUtils for platform detection and shell command handling. - Centralized TypeScript types in index.ts for better organization. - Implemented compatibility checks for hook templates based on platform requirements.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
// ========================================
|
||||
// A2UI Component Registry
|
||||
// ========================================
|
||||
// Maps A2UI component types to React renderer functions
|
||||
|
||||
import type { A2UIComponent, A2UIComponentType } from './A2UITypes';
|
||||
|
||||
// ========== Renderer Types ==========
|
||||
|
||||
/** State object for A2UI surfaces */
|
||||
export type A2UIState = Record<string, unknown>;
|
||||
|
||||
/** Action handler callback */
|
||||
export type ActionHandler = (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
|
||||
/** Binding resolver function */
|
||||
export type BindingResolver = (binding: { path: string }) => unknown;
|
||||
|
||||
/** Component renderer function */
|
||||
export type ComponentRenderer = (props: {
|
||||
component: A2UIComponent;
|
||||
state: A2UIState;
|
||||
onAction: ActionHandler;
|
||||
resolveBinding: BindingResolver;
|
||||
}) => JSX.Element | null;
|
||||
|
||||
// ========== Registry Class ==========
|
||||
|
||||
/**
|
||||
* A2UI Component Registry
|
||||
* Maps A2UI component types to React renderer functions
|
||||
*/
|
||||
export class A2UIComponentRegistry {
|
||||
private readonly renderers = new Map<A2UIComponentType, ComponentRenderer>();
|
||||
|
||||
/**
|
||||
* Register a component renderer
|
||||
* @param type - Component type name (e.g., 'Text', 'Button')
|
||||
* @param renderer - React component function
|
||||
*/
|
||||
register(type: A2UIComponentType, renderer: ComponentRenderer): void {
|
||||
this.renderers.set(type, renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a component renderer
|
||||
* @param type - Component type name
|
||||
*/
|
||||
unregister(type: A2UIComponentType): void {
|
||||
this.renderers.delete(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a component renderer
|
||||
* @param type - Component type name
|
||||
* @returns Renderer function or undefined if not registered
|
||||
*/
|
||||
get(type: A2UIComponentType): ComponentRenderer | undefined {
|
||||
return this.renderers.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a component type is registered
|
||||
* @param type - Component type name
|
||||
* @returns True if renderer exists
|
||||
*/
|
||||
has(type: A2UIComponentType): boolean {
|
||||
return this.renderers.has(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered component types
|
||||
* @returns Array of registered type names
|
||||
*/
|
||||
getRegisteredTypes(): A2UIComponentType[] {
|
||||
return Array.from(this.renderers.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all registered renderers
|
||||
*/
|
||||
clear(): void {
|
||||
this.renderers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of registered renderers
|
||||
* @returns Count of registered renderers
|
||||
*/
|
||||
get size(): number {
|
||||
return this.renderers.size;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Singleton Export ==========
|
||||
|
||||
/** Global component registry instance */
|
||||
export const a2uiRegistry = new A2UIComponentRegistry();
|
||||
|
||||
// ========== Built-in Component Registration ==========
|
||||
|
||||
/**
|
||||
* Initialize built-in component renderers
|
||||
* Called from renderer components index to avoid circular dependencies
|
||||
*/
|
||||
export function initializeBuiltInComponents(): void {
|
||||
// Deferred import to avoid circular dependencies
|
||||
// This will be called from renderer/components/index.ts
|
||||
// after all component implementations are loaded
|
||||
}
|
||||
142
ccw/frontend/src/packages/a2ui-runtime/core/A2UIParser.ts
Normal file
142
ccw/frontend/src/packages/a2ui-runtime/core/A2UIParser.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
// ========================================
|
||||
// A2UI Protocol Parser
|
||||
// ========================================
|
||||
// Parses and validates A2UI surface update JSON
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
SurfaceUpdateSchema,
|
||||
SurfaceUpdate,
|
||||
ComponentSchema,
|
||||
A2UIComponent,
|
||||
} from './A2UITypes';
|
||||
|
||||
// ========== Error Class ==========
|
||||
|
||||
/** Custom error for A2UI parsing failures */
|
||||
export class A2UIParseError extends Error {
|
||||
public readonly originalError?: unknown;
|
||||
|
||||
constructor(message: string, originalError?: unknown) {
|
||||
super(message);
|
||||
this.name = 'A2UIParseError';
|
||||
this.originalError = originalError;
|
||||
}
|
||||
|
||||
/** Get detailed error information */
|
||||
getDetails(): string {
|
||||
if (this.originalError instanceof z.ZodError) {
|
||||
return this.originalError.issues
|
||||
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
||||
.join('; ');
|
||||
}
|
||||
if (this.originalError instanceof Error) {
|
||||
return this.originalError.message;
|
||||
}
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Parser Class ==========
|
||||
|
||||
/**
|
||||
* A2UI Protocol Parser
|
||||
* Parses JSON strings into validated SurfaceUpdate objects
|
||||
*/
|
||||
export class A2UIParser {
|
||||
/**
|
||||
* Parse JSON string into SurfaceUpdate
|
||||
* @param json - JSON string to parse
|
||||
* @returns Validated SurfaceUpdate object
|
||||
* @throws A2UIParseError if JSON is invalid or doesn't match schema
|
||||
*/
|
||||
parse(json: string): SurfaceUpdate {
|
||||
try {
|
||||
// First, parse JSON
|
||||
const data = JSON.parse(json);
|
||||
|
||||
// Then validate against schema
|
||||
return SurfaceUpdateSchema.parse(data);
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
throw new A2UIParseError(`Invalid JSON: ${error.message}`, error);
|
||||
}
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new A2UIParseError(
|
||||
`A2UI validation failed: ${error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', ')}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
throw new A2UIParseError(`Failed to parse A2UI surface: ${error instanceof Error ? error.message : String(error)}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse object into SurfaceUpdate
|
||||
* @param data - Object to validate
|
||||
* @returns Validated SurfaceUpdate object
|
||||
* @throws A2UIParseError if object doesn't match schema
|
||||
*/
|
||||
parseObject(data: unknown): SurfaceUpdate {
|
||||
try {
|
||||
return SurfaceUpdateSchema.parse(data);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new A2UIParseError(
|
||||
`A2UI validation failed: ${error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', ')}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
throw new A2UIParseError(`Failed to validate A2UI surface: ${error instanceof Error ? error.message : String(error)}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if value is a valid SurfaceUpdate
|
||||
* @param value - Value to check
|
||||
* @returns True if value is a valid SurfaceUpdate
|
||||
*/
|
||||
validate(value: unknown): value is SurfaceUpdate {
|
||||
return SurfaceUpdateSchema.safeParse(value).success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe parse that returns result instead of throwing
|
||||
* @param json - JSON string to parse
|
||||
* @returns Result object with success flag and data or error
|
||||
*/
|
||||
safeParse(json: string): z.SafeParseReturnType<SurfaceUpdate, SurfaceUpdate> {
|
||||
try {
|
||||
const data = JSON.parse(json);
|
||||
return SurfaceUpdateSchema.safeParse(data);
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe parse object that returns result instead of throwing
|
||||
* @param data - Object to validate
|
||||
* @returns Result object with success flag and data or error
|
||||
*/
|
||||
safeParseObject(data: unknown): z.SafeParseReturnType<SurfaceUpdate, SurfaceUpdate> {
|
||||
return SurfaceUpdateSchema.safeParse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a component against the component schema
|
||||
* @param component - Component to validate
|
||||
* @returns True if component is valid
|
||||
*/
|
||||
validateComponent(component: unknown): component is A2UIComponent {
|
||||
return ComponentSchema.safeParse(component).success;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Singleton Export ==========
|
||||
|
||||
/** Default parser instance */
|
||||
export const a2uiParser = new A2UIParser();
|
||||
212
ccw/frontend/src/packages/a2ui-runtime/core/A2UITypes.ts
Normal file
212
ccw/frontend/src/packages/a2ui-runtime/core/A2UITypes.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
// ========================================
|
||||
// A2UI Runtime Type Definitions
|
||||
// ========================================
|
||||
// Zod schemas and TypeScript interfaces for A2UI protocol
|
||||
// Based on Google's A2UI specification
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
// ========== Primitive Content Schemas ==========
|
||||
|
||||
/** Literal string content */
|
||||
export const LiteralStringSchema = z.object({
|
||||
literalString: z.string(),
|
||||
});
|
||||
|
||||
/** Binding content - references state by path */
|
||||
export const BindingSchema = z.object({
|
||||
path: z.string(),
|
||||
});
|
||||
|
||||
/** Text content can be literal or bound to state */
|
||||
export const TextContentSchema = z.union([
|
||||
LiteralStringSchema,
|
||||
BindingSchema,
|
||||
]);
|
||||
|
||||
/** Number content can be literal or bound to state */
|
||||
export const NumberContentSchema = z.union([
|
||||
z.object({ literalNumber: z.number() }),
|
||||
BindingSchema,
|
||||
]);
|
||||
|
||||
/** Boolean content can be literal or bound to state */
|
||||
export const BooleanContentSchema = z.union([
|
||||
z.object({ literalBoolean: z.boolean() }),
|
||||
BindingSchema,
|
||||
]);
|
||||
|
||||
// ========== Component Schemas ==========
|
||||
|
||||
/** Action trigger */
|
||||
export const ActionSchema = z.object({
|
||||
actionId: z.string(),
|
||||
parameters: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
/** Text component */
|
||||
export const TextComponentSchema = z.object({
|
||||
Text: z.object({
|
||||
text: TextContentSchema,
|
||||
usageHint: z.enum(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'code', 'small']).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Button component */
|
||||
export const ButtonComponentSchema = z.object({
|
||||
Button: z.object({
|
||||
onClick: ActionSchema,
|
||||
content: z.lazy(() => ComponentSchema),
|
||||
variant: z.enum(['primary', 'secondary', 'destructive', 'ghost', 'outline']).optional(),
|
||||
disabled: BooleanContentSchema.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Dropdown/Select component */
|
||||
export const DropdownComponentSchema = z.object({
|
||||
Dropdown: z.object({
|
||||
options: z.array(z.object({
|
||||
label: TextContentSchema,
|
||||
value: z.string(),
|
||||
})),
|
||||
selectedValue: TextContentSchema.optional(),
|
||||
onChange: ActionSchema,
|
||||
placeholder: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Text input component */
|
||||
export const TextFieldComponentSchema = z.object({
|
||||
TextField: z.object({
|
||||
value: TextContentSchema.optional(),
|
||||
onChange: ActionSchema,
|
||||
placeholder: z.string().optional(),
|
||||
type: z.enum(['text', 'email', 'password', 'number', 'url']).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Text area component */
|
||||
export const TextAreaComponentSchema = z.object({
|
||||
TextArea: z.object({
|
||||
value: TextContentSchema.optional(),
|
||||
onChange: ActionSchema,
|
||||
placeholder: z.string().optional(),
|
||||
rows: z.number().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Checkbox component */
|
||||
export const CheckboxComponentSchema = z.object({
|
||||
Checkbox: z.object({
|
||||
checked: BooleanContentSchema.optional(),
|
||||
onChange: ActionSchema,
|
||||
label: TextContentSchema.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Code block component */
|
||||
export const CodeBlockComponentSchema = z.object({
|
||||
CodeBlock: z.object({
|
||||
code: TextContentSchema,
|
||||
language: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Progress component */
|
||||
export const ProgressComponentSchema = z.object({
|
||||
Progress: z.object({
|
||||
value: NumberContentSchema.optional(),
|
||||
max: z.number().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
/** Card container component */
|
||||
export const CardComponentSchema = z.object({
|
||||
Card: z.object({
|
||||
content: z.array(z.lazy(() => ComponentSchema)),
|
||||
title: TextContentSchema.optional(),
|
||||
description: TextContentSchema.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
// ========== Component Union ==========
|
||||
|
||||
/** All A2UI component types */
|
||||
export const ComponentSchema: z.ZodType<
|
||||
| z.infer<typeof TextComponentSchema>
|
||||
| z.infer<typeof ButtonComponentSchema>
|
||||
| z.infer<typeof DropdownComponentSchema>
|
||||
| z.infer<typeof TextFieldComponentSchema>
|
||||
| z.infer<typeof TextAreaComponentSchema>
|
||||
| z.infer<typeof CheckboxComponentSchema>
|
||||
| z.infer<typeof CodeBlockComponentSchema>
|
||||
| z.infer<typeof ProgressComponentSchema>
|
||||
| z.infer<typeof CardComponentSchema>
|
||||
> = z.union([
|
||||
TextComponentSchema,
|
||||
ButtonComponentSchema,
|
||||
DropdownComponentSchema,
|
||||
TextFieldComponentSchema,
|
||||
TextAreaComponentSchema,
|
||||
CheckboxComponentSchema,
|
||||
CodeBlockComponentSchema,
|
||||
ProgressComponentSchema,
|
||||
CardComponentSchema,
|
||||
]);
|
||||
|
||||
// ========== Surface Schemas ==========
|
||||
|
||||
/** Surface component with ID */
|
||||
export const SurfaceComponentSchema = z.object({
|
||||
id: z.string(),
|
||||
component: ComponentSchema,
|
||||
});
|
||||
|
||||
/** Surface update message */
|
||||
export const SurfaceUpdateSchema = z.object({
|
||||
surfaceId: z.string(),
|
||||
components: z.array(SurfaceComponentSchema),
|
||||
initialState: z.record(z.unknown()).optional(),
|
||||
});
|
||||
|
||||
// ========== TypeScript Types ==========
|
||||
|
||||
export type LiteralString = z.infer<typeof LiteralStringSchema>;
|
||||
export type Binding = z.infer<typeof BindingSchema>;
|
||||
export type TextContent = z.infer<typeof TextContentSchema>;
|
||||
export type NumberContent = z.infer<typeof NumberContentSchema>;
|
||||
export type BooleanContent = z.infer<typeof BooleanContentSchema>;
|
||||
export type Action = z.infer<typeof ActionSchema>;
|
||||
|
||||
export type TextComponent = z.infer<typeof TextComponentSchema>;
|
||||
export type ButtonComponent = z.infer<typeof ButtonComponentSchema>;
|
||||
export type DropdownComponent = z.infer<typeof DropdownComponentSchema>;
|
||||
export type TextFieldComponent = z.infer<typeof TextFieldComponentSchema>;
|
||||
export type TextAreaComponent = z.infer<typeof TextAreaComponentSchema>;
|
||||
export type CheckboxComponent = z.infer<typeof CheckboxComponentSchema>;
|
||||
export type CodeBlockComponent = z.infer<typeof CodeBlockComponentSchema>;
|
||||
export type ProgressComponent = z.infer<typeof ProgressComponentSchema>;
|
||||
export type CardComponent = z.infer<typeof CardComponentSchema>;
|
||||
|
||||
export type A2UIComponent = z.infer<typeof ComponentSchema>;
|
||||
export type SurfaceComponent = z.infer<typeof SurfaceComponentSchema>;
|
||||
export type SurfaceUpdate = z.infer<typeof SurfaceUpdateSchema>;
|
||||
|
||||
// ========== Helper Types ==========
|
||||
|
||||
/** Discriminated union for component type detection */
|
||||
export type A2UIComponentType =
|
||||
| 'Text'
|
||||
| 'Button'
|
||||
| 'Dropdown'
|
||||
| 'TextField'
|
||||
| 'TextArea'
|
||||
| 'Checkbox'
|
||||
| 'CodeBlock'
|
||||
| 'Progress'
|
||||
| 'Card';
|
||||
|
||||
/** Get component type from discriminated union */
|
||||
export function getComponentType(component: A2UIComponent): A2UIComponentType {
|
||||
return Object.keys(component)[0] as A2UIComponentType;
|
||||
}
|
||||
7
ccw/frontend/src/packages/a2ui-runtime/core/index.ts
Normal file
7
ccw/frontend/src/packages/a2ui-runtime/core/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// ========================================
|
||||
// A2UI Runtime Core - Index
|
||||
// ========================================
|
||||
|
||||
export * from './A2UITypes';
|
||||
export * from './A2UIParser';
|
||||
export * from './A2UIComponentRegistry';
|
||||
6
ccw/frontend/src/packages/a2ui-runtime/index.ts
Normal file
6
ccw/frontend/src/packages/a2ui-runtime/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// ========================================
|
||||
// A2UI Runtime - Main Index
|
||||
// ========================================
|
||||
|
||||
export * from './core';
|
||||
export * from './renderer';
|
||||
184
ccw/frontend/src/packages/a2ui-runtime/renderer/A2UIRenderer.tsx
Normal file
184
ccw/frontend/src/packages/a2ui-runtime/renderer/A2UIRenderer.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
// ========================================
|
||||
// A2UI Renderer Component
|
||||
// ========================================
|
||||
// React component that renders A2UI surfaces
|
||||
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import type { SurfaceUpdate, SurfaceComponent, A2UIComponent, LiteralString, Binding } from '../core/A2UITypes';
|
||||
import { a2uiRegistry, type A2UIState, type ActionHandler, type BindingResolver } from '../core/A2UIComponentRegistry';
|
||||
|
||||
// ========== Renderer Props ==========
|
||||
|
||||
interface A2UIRendererProps {
|
||||
/** Surface update to render */
|
||||
surface: SurfaceUpdate;
|
||||
/** Optional external action handler */
|
||||
onAction?: ActionHandler;
|
||||
/** Optional className for the container */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// ========== Main Renderer Component ==========
|
||||
|
||||
/**
|
||||
* A2UI Surface Renderer
|
||||
* Renders A2UI surface updates as interactive React components
|
||||
*/
|
||||
export function A2UIRenderer({ surface, onAction, className = '' }: A2UIRendererProps) {
|
||||
// Local state initialized with surface's initial state
|
||||
const [localState, setLocalState] = useState<A2UIState>(surface.initialState || {});
|
||||
|
||||
// Handle action from components
|
||||
const handleAction = useCallback<ActionHandler>(
|
||||
async (actionId, params) => {
|
||||
if (onAction) {
|
||||
await onAction(actionId, { ...params, ...localState });
|
||||
}
|
||||
},
|
||||
[onAction, localState]
|
||||
);
|
||||
|
||||
// Resolve binding path to state value
|
||||
const resolveBinding = useCallback<BindingResolver>(
|
||||
(binding) => {
|
||||
const path = binding.path.replace(/^\//, '').split('/');
|
||||
let value: unknown = localState;
|
||||
|
||||
for (const key of path) {
|
||||
if (value && typeof value === 'object' && key in value) {
|
||||
value = (value as Record<string, unknown>)[key];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
[localState]
|
||||
);
|
||||
|
||||
// Update state from external source
|
||||
const updateState = useCallback((updates: Partial<A2UIState>) => {
|
||||
setLocalState((prev) => ({ ...prev, ...updates }));
|
||||
}, []);
|
||||
|
||||
// Memoize context for components
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
state: localState,
|
||||
resolveBinding,
|
||||
updateState,
|
||||
}),
|
||||
[localState, resolveBinding, updateState]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`a2ui-surface ${className}`} data-surface-id={surface.surfaceId}>
|
||||
{surface.components.map((comp) => (
|
||||
<ComponentRenderer
|
||||
key={comp.id}
|
||||
id={comp.id}
|
||||
component={comp.component}
|
||||
state={localState}
|
||||
onAction={handleAction}
|
||||
resolveBinding={resolveBinding}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ========== Component Renderer ==========
|
||||
|
||||
interface ComponentRendererProps {
|
||||
id: string;
|
||||
component: A2UIComponent;
|
||||
state: A2UIState;
|
||||
onAction: ActionHandler;
|
||||
resolveBinding: BindingResolver;
|
||||
}
|
||||
|
||||
function ComponentRenderer({ id, component, state, onAction, resolveBinding }: ComponentRendererProps): JSX.Element | null {
|
||||
// Get component type (discriminated union key)
|
||||
const componentType = Object.keys(component)[0];
|
||||
|
||||
// Get renderer from registry
|
||||
const renderer = a2uiRegistry.get(componentType as any);
|
||||
|
||||
if (!renderer) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(`[A2UIRenderer] Unknown component type: ${componentType}`);
|
||||
}
|
||||
return (
|
||||
<div className="a2ui-error" data-component-id={id}>
|
||||
Unknown component type: {componentType}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Render component
|
||||
try {
|
||||
return renderer({ component, state, onAction, resolveBinding });
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error(`[A2UIRenderer] Error rendering component ${id}:`, error);
|
||||
}
|
||||
return (
|
||||
<div className="a2ui-error" data-component-id={id}>
|
||||
Error rendering component: {error instanceof Error ? error.message : String(error)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
/**
|
||||
* Resolve literal or binding to actual value
|
||||
* @param content - Content to resolve (literal or binding)
|
||||
* @param resolveBinding - Binding resolver function
|
||||
* @returns Resolved value
|
||||
*/
|
||||
export function resolveLiteralOrBinding(
|
||||
content: LiteralString | Binding | { literalNumber?: number; literalBoolean?: boolean },
|
||||
resolveBinding: BindingResolver
|
||||
): string | number | boolean {
|
||||
// Check for literal string
|
||||
if ('literalString' in content) {
|
||||
return content.literalString;
|
||||
}
|
||||
|
||||
// Check for literal number
|
||||
if ('literalNumber' in content && typeof content.literalNumber === 'number') {
|
||||
return content.literalNumber;
|
||||
}
|
||||
|
||||
// Check for literal boolean
|
||||
if ('literalBoolean' in content && typeof content.literalBoolean === 'boolean') {
|
||||
return content.literalBoolean;
|
||||
}
|
||||
|
||||
// Resolve binding
|
||||
const value = resolveBinding(content as Binding);
|
||||
|
||||
// Return resolved value or empty string as fallback
|
||||
return value ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve text content to string
|
||||
* @param content - Text content to resolve
|
||||
* @param resolveBinding - Binding resolver function
|
||||
* @returns Resolved string value
|
||||
*/
|
||||
export function resolveTextContent(
|
||||
content: LiteralString | Binding,
|
||||
resolveBinding: BindingResolver
|
||||
): string {
|
||||
const value = resolveLiteralOrBinding(content, resolveBinding);
|
||||
return String(value ?? '');
|
||||
}
|
||||
|
||||
// ========== Export Helper Types ==========
|
||||
|
||||
export type { A2UIState, ActionHandler, BindingResolver };
|
||||
@@ -0,0 +1,73 @@
|
||||
// ========================================
|
||||
// A2UI Button Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI Button component to shadcn/ui Button
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import type { A2UIState, ActionHandler, BindingResolver } from '../../core/A2UIComponentRegistry';
|
||||
import type { ButtonComponent, A2UIComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UIButtonRendererProps {
|
||||
component: A2UIComponent;
|
||||
state: A2UIState;
|
||||
onAction: ActionHandler;
|
||||
resolveBinding: BindingResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Button Component Renderer
|
||||
* Maps A2UI variants (primary/secondary/destructive) to shadcn/ui variants (default/secondary/destructive/ghost)
|
||||
*/
|
||||
export const A2UIButton: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const buttonComp = component as ButtonComponent;
|
||||
const { Button: buttonConfig } = buttonComp;
|
||||
|
||||
// Resolve content (nested component - typically Text)
|
||||
const ContentComponent = () => {
|
||||
const contentType = Object.keys(buttonConfig.content)[0];
|
||||
if (contentType === 'Text') {
|
||||
const text = buttonConfig.content.Text.text;
|
||||
const resolved = typeof text === 'string' && text.startsWith('{')
|
||||
? resolveBinding({ path: JSON.parse(text).path })
|
||||
: text;
|
||||
return <>{resolved}</>;
|
||||
}
|
||||
return <>{contentType}</>;
|
||||
};
|
||||
|
||||
// Map A2UI variants to shadcn/ui variants
|
||||
// A2UI: primary, secondary, destructive, ghost, outline
|
||||
// shadcn/ui: default, secondary, destructive, ghost, outline
|
||||
const variantMap: Record<string, 'default' | 'secondary' | 'destructive' | 'ghost' | 'outline'> = {
|
||||
primary: 'default',
|
||||
secondary: 'secondary',
|
||||
destructive: 'destructive',
|
||||
ghost: 'ghost',
|
||||
outline: 'outline',
|
||||
};
|
||||
|
||||
const variant = buttonConfig.variant ? variantMap[buttonConfig.variant] ?? 'default' : 'default';
|
||||
|
||||
// Resolve disabled state
|
||||
let disabled = false;
|
||||
if (buttonConfig.disabled) {
|
||||
if (typeof buttonConfig.disabled === 'object' && 'literalBoolean' in buttonConfig.disabled) {
|
||||
disabled = buttonConfig.disabled.literalBoolean === false;
|
||||
} else if (typeof buttonConfig.disabled === 'object' && 'path' in buttonConfig.disabled) {
|
||||
const resolved = resolveBinding(buttonConfig.disabled);
|
||||
disabled = resolved !== true;
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
onAction(buttonConfig.onClick.actionId, buttonConfig.onClick.parameters || {});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button variant={variant} disabled={disabled} onClick={handleClick}>
|
||||
<ContentComponent />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
// ========================================
|
||||
// A2UI Card Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI Card component to shadcn/ui Card
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/Card';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveTextContent } from '../A2UIRenderer';
|
||||
import type { CardComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UICardProps {
|
||||
component: CardComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Card Component Renderer
|
||||
* Container component with optional title and description
|
||||
*/
|
||||
export const A2UICard: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const cardComp = component as CardComponent;
|
||||
const { Card: cardConfig } = cardComp;
|
||||
|
||||
// Resolve title and description
|
||||
const title = cardConfig.title ? resolveTextContent(cardConfig.title, resolveBinding) : undefined;
|
||||
const description = cardConfig.description ? resolveTextContent(cardConfig.description, resolveBinding) : undefined;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{(title || description) && (
|
||||
<CardHeader>
|
||||
{title && <CardTitle>{title}</CardTitle>}
|
||||
{description && <CardDescription>{description}</CardDescription>}
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardContent>
|
||||
{cardConfig.content.map((childComp, index) => {
|
||||
// For nested components, we would typically use the registry
|
||||
// But for simplicity in this renderer, we just render a placeholder
|
||||
const childType = Object.keys(childComp)[0];
|
||||
return (
|
||||
<div key={index} data-component-type={childType}>
|
||||
{/* Nested components would be rendered here via A2UIRenderer */}
|
||||
{childType}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
// ========================================
|
||||
// A2UI Checkbox Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI Checkbox component to shadcn/ui Checkbox
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Checkbox } from '@/components/ui/Checkbox';
|
||||
import { Label } from '@/components/ui/Label';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding, resolveTextContent } from '../A2UIRenderer';
|
||||
import type { CheckboxComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UICheckboxProps {
|
||||
component: CheckboxComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Checkbox Component Renderer
|
||||
* Boolean state binding with onChange handler
|
||||
*/
|
||||
export const A2UICheckbox: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const checkboxComp = component as CheckboxComponent;
|
||||
const { Checkbox: checkboxConfig } = checkboxComp;
|
||||
|
||||
// Resolve initial checked state from binding
|
||||
const getInitialChecked = (): boolean => {
|
||||
if (!checkboxConfig.checked) return false;
|
||||
const resolved = resolveLiteralOrBinding(checkboxConfig.checked, resolveBinding);
|
||||
return Boolean(resolved);
|
||||
};
|
||||
|
||||
// Local state for controlled checkbox
|
||||
const [checked, setChecked] = useState(getInitialChecked());
|
||||
|
||||
// Handle change with two-way binding
|
||||
const handleChange = useCallback((newChecked: boolean) => {
|
||||
setChecked(newChecked);
|
||||
|
||||
// Trigger action with new checked state
|
||||
onAction(checkboxConfig.onChange.actionId, {
|
||||
checked: newChecked,
|
||||
...(checkboxConfig.onChange.parameters || {}),
|
||||
});
|
||||
}, [checkboxConfig.onChange, onAction]);
|
||||
|
||||
// Resolve label text
|
||||
const labelText = checkboxConfig.label
|
||||
? resolveTextContent(checkboxConfig.label, resolveBinding)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
onCheckedChange={handleChange}
|
||||
/>
|
||||
{labelText && (
|
||||
<Label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||
{labelText}
|
||||
</Label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
// ========================================
|
||||
// A2UI Dropdown Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI Dropdown component to shadcn/ui Select
|
||||
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/Select';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveTextContent } from '../A2UIRenderer';
|
||||
import type { DropdownComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UIDropdownProps {
|
||||
component: DropdownComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Dropdown Component Renderer
|
||||
* Using shadcn/ui Select with options array mapping to SelectItem
|
||||
*/
|
||||
export const A2UIDropdown: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const dropdownComp = component as DropdownComponent;
|
||||
const { Dropdown: dropdownConfig } = dropdownComp;
|
||||
|
||||
// Resolve initial selected value from binding
|
||||
const getInitialValue = (): string => {
|
||||
if (!dropdownConfig.selectedValue) return '';
|
||||
const resolved = resolveTextContent(dropdownConfig.selectedValue, resolveBinding);
|
||||
return resolved;
|
||||
};
|
||||
|
||||
// Local state for controlled select
|
||||
const [selectedValue, setSelectedValue] = useState(getInitialValue);
|
||||
|
||||
// Update local state when selectedValue binding changes
|
||||
useEffect(() => {
|
||||
setSelectedValue(getInitialValue());
|
||||
}, [dropdownConfig.selectedValue, state]);
|
||||
|
||||
// Handle change with two-way binding
|
||||
const handleChange = useCallback((newValue: string) => {
|
||||
setSelectedValue(newValue);
|
||||
|
||||
// Trigger action with new selected value
|
||||
onAction(dropdownConfig.onChange.actionId, {
|
||||
value: newValue,
|
||||
...(dropdownConfig.onChange.parameters || {}),
|
||||
});
|
||||
}, [dropdownConfig.onChange, onAction]);
|
||||
|
||||
return (
|
||||
<Select value={selectedValue} onValueChange={handleChange}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={dropdownConfig.placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{dropdownConfig.options.map((option) => {
|
||||
const label = resolveTextContent(option.label, resolveBinding);
|
||||
return (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
// ========================================
|
||||
// A2UI Progress Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI Progress component to shadcn/ui Progress
|
||||
|
||||
import React from 'react';
|
||||
import { Progress } from '@/components/ui/Progress';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding } from '../A2UIRenderer';
|
||||
import type { ProgressComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UIProgressProps {
|
||||
component: ProgressComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Progress Component Renderer
|
||||
* For CLI output progress display
|
||||
*/
|
||||
export const A2UIProgress: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const progressComp = component as ProgressComponent;
|
||||
const { Progress: progressConfig } = progressComp;
|
||||
|
||||
// Resolve value and max from bindings or use defaults
|
||||
const value = progressConfig.value
|
||||
? Number(resolveLiteralOrBinding(progressConfig.value, resolveBinding) ?? 0)
|
||||
: 0;
|
||||
const max = progressConfig.max ?? 100;
|
||||
|
||||
// Calculate percentage for display (0-100)
|
||||
const percentage = max > 0 ? Math.min(100, Math.max(0, (value / max) * 100)) : 0;
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-1">
|
||||
<Progress value={percentage} max={100} />
|
||||
<div className="flex justify-between text-xs text-muted-foreground">
|
||||
<span>Progress</span>
|
||||
<span>{Math.round(percentage)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
// ========================================
|
||||
// A2UI Text Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI Text component to HTML elements
|
||||
|
||||
import React from 'react';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveTextContent } from '../A2UIRenderer';
|
||||
import type { TextComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UITextProps {
|
||||
component: TextComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI Text Component Renderer
|
||||
* Maps A2UI Text usageHint to HTML elements (h1, h2, h3, p, span, code)
|
||||
*/
|
||||
export const A2UIText: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const { Text } = component as { Text: { text: unknown; usageHint?: string } };
|
||||
|
||||
// Resolve text content
|
||||
const text = resolveTextContent(Text.text, resolveBinding);
|
||||
const usageHint = Text.usageHint || 'span';
|
||||
|
||||
// Map usageHint to HTML elements
|
||||
const elementMap: Record<string, React.ElementType> = {
|
||||
h1: 'h1',
|
||||
h2: 'h2',
|
||||
h3: 'h3',
|
||||
h4: 'h4',
|
||||
h5: 'h5',
|
||||
h6: 'h6',
|
||||
p: 'p',
|
||||
span: 'span',
|
||||
code: 'code',
|
||||
small: 'small',
|
||||
};
|
||||
|
||||
// Map usageHint to Tailwind classes
|
||||
const classMap: Record<string, string> = {
|
||||
h1: 'text-2xl font-bold leading-tight',
|
||||
h2: 'text-xl font-bold leading-tight',
|
||||
h3: 'text-lg font-semibold leading-tight',
|
||||
h4: 'text-base font-semibold leading-tight',
|
||||
h5: 'text-sm font-semibold leading-tight',
|
||||
h6: 'text-xs font-semibold leading-tight',
|
||||
p: 'text-sm leading-relaxed',
|
||||
span: 'text-sm',
|
||||
code: 'font-mono text-sm bg-muted px-1 py-0.5 rounded',
|
||||
small: 'text-xs text-muted-foreground',
|
||||
};
|
||||
|
||||
const ElementType = elementMap[usageHint] || 'span';
|
||||
const className = classMap[usageHint] || classMap.span;
|
||||
|
||||
return <ElementType className={className}>{text}</ElementType>;
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// ========================================
|
||||
// A2UI TextArea Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI TextArea component to shadcn/ui Textarea
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Textarea } from '@/components/ui/Textarea';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding } from '../A2UIRenderer';
|
||||
import type { TextAreaComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UITextAreaProps {
|
||||
component: TextAreaComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI TextArea Component Renderer
|
||||
* Two-way binding via onChange updates to local state
|
||||
*/
|
||||
export const A2UITextArea: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const areaComp = component as TextAreaComponent;
|
||||
const { TextArea: areaConfig } = areaComp;
|
||||
|
||||
// Resolve initial value from binding or use empty string
|
||||
const initialValue = areaConfig.value
|
||||
? String(resolveLiteralOrBinding(areaConfig.value, resolveBinding) ?? '')
|
||||
: '';
|
||||
|
||||
// Local state for controlled input
|
||||
const [localValue, setLocalValue] = useState(initialValue);
|
||||
|
||||
// Handle change with two-way binding
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalValue(newValue);
|
||||
|
||||
// Trigger action with new value
|
||||
onAction(areaConfig.onChange.actionId, {
|
||||
value: newValue,
|
||||
...(areaConfig.onChange.parameters || {}),
|
||||
});
|
||||
}, [areaConfig.onChange, onAction]);
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
value={localValue}
|
||||
onChange={handleChange}
|
||||
placeholder={areaConfig.placeholder}
|
||||
rows={areaConfig.rows}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// ========================================
|
||||
// A2UI TextField Component Renderer
|
||||
// ========================================
|
||||
// Maps A2UI TextField component to shadcn/ui Input
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import type { ComponentRenderer } from '../../core/A2UIComponentRegistry';
|
||||
import { resolveLiteralOrBinding } from '../A2UIRenderer';
|
||||
import type { TextFieldComponent } from '../../core/A2UITypes';
|
||||
|
||||
interface A2UITextFieldProps {
|
||||
component: TextFieldComponent;
|
||||
state: Record<string, unknown>;
|
||||
onAction: (actionId: string, params: Record<string, unknown>) => void | Promise<void>;
|
||||
resolveBinding: (binding: { path: string }) => unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A2UI TextField Component Renderer
|
||||
* Two-way binding via onChange updates to local state
|
||||
*/
|
||||
export const A2UITextField: ComponentRenderer = ({ component, state, onAction, resolveBinding }) => {
|
||||
const fieldComp = component as TextFieldComponent;
|
||||
const { TextField: fieldConfig } = fieldComp;
|
||||
|
||||
// Resolve initial value from binding or use empty string
|
||||
const initialValue = fieldConfig.value
|
||||
? String(resolveLiteralOrBinding(fieldConfig.value, resolveBinding) ?? '')
|
||||
: '';
|
||||
|
||||
// Local state for controlled input
|
||||
const [localValue, setLocalValue] = useState(initialValue);
|
||||
|
||||
// Handle change with two-way binding
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalValue(newValue);
|
||||
|
||||
// Trigger action with new value
|
||||
onAction(fieldConfig.onChange.actionId, {
|
||||
value: newValue,
|
||||
...(fieldConfig.onChange.parameters || {}),
|
||||
});
|
||||
}, [fieldConfig.onChange, onAction]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
type={fieldConfig.type || 'text'}
|
||||
value={localValue}
|
||||
onChange={handleChange}
|
||||
placeholder={fieldConfig.placeholder}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
// ========================================
|
||||
// A2UI Component Renderers Index
|
||||
// ========================================
|
||||
// Exports all A2UI component renderers
|
||||
|
||||
export * from './registry';
|
||||
@@ -0,0 +1,44 @@
|
||||
// ========================================
|
||||
// A2UI Component Registry Initialization
|
||||
// ========================================
|
||||
// Registers all A2UI component renderers
|
||||
|
||||
import { a2uiRegistry } from '../../core/A2UIComponentRegistry';
|
||||
import { A2UIText } from './A2UIText';
|
||||
import { A2UIButton } from './A2UIButton';
|
||||
import { A2UIDropdown } from './A2UIDropdown';
|
||||
import { A2UITextField } from './A2UITextField';
|
||||
import { A2UITextArea } from './A2UITextArea';
|
||||
import { A2UICheckbox } from './A2UICheckbox';
|
||||
import { A2UIProgress } from './A2UIProgress';
|
||||
import { A2UICard } from './A2UICard';
|
||||
|
||||
/**
|
||||
* Initialize and register all built-in A2UI component renderers
|
||||
*/
|
||||
export function registerBuiltInComponents(): void {
|
||||
// Register all component types
|
||||
a2uiRegistry.register('Text', A2UIText);
|
||||
a2uiRegistry.register('Button', A2UIButton);
|
||||
a2uiRegistry.register('Dropdown', A2UIDropdown);
|
||||
a2uiRegistry.register('TextField', A2UITextField);
|
||||
a2uiRegistry.register('TextArea', A2UITextArea);
|
||||
a2uiRegistry.register('Checkbox', A2UICheckbox);
|
||||
a2uiRegistry.register('Progress', A2UIProgress);
|
||||
a2uiRegistry.register('Card', A2UICard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-initialize on import
|
||||
* This ensures all components are registered when the renderer package is loaded
|
||||
*/
|
||||
registerBuiltInComponents();
|
||||
|
||||
export * from './A2UIText';
|
||||
export * from './A2UIButton';
|
||||
export * from './A2UIDropdown';
|
||||
export * from './A2UITextField';
|
||||
export * from './A2UITextArea';
|
||||
export * from './A2UICheckbox';
|
||||
export * from './A2UIProgress';
|
||||
export * from './A2UICard';
|
||||
5
ccw/frontend/src/packages/a2ui-runtime/renderer/index.ts
Normal file
5
ccw/frontend/src/packages/a2ui-runtime/renderer/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// ========================================
|
||||
// A2UI Runtime Renderer - Index
|
||||
// ========================================
|
||||
|
||||
export * from './A2UIRenderer';
|
||||
Reference in New Issue
Block a user