import { default as React } from 'react'; import { StatusLevel } from '../../utils'; /** Status alert block — renders as a colored banner with icon shape */ export interface ChatBlockAlert { type: "alert"; status: StatusLevel; title: string; detail?: string; } /** Telemetry readout block — key/value pairs with optional status per row */ export interface ChatBlockTelemetry { type: "telemetry"; title?: string; items: Array<{ label: string; value: string | number; unit?: string; status?: StatusLevel; }>; } /** Progress block — inline progress bar */ export interface ChatBlockProgress { type: "progress"; label: string; value: number; max?: number; status?: StatusLevel; } /** Data table block */ export interface ChatBlockTable { type: "table"; title?: string; columns: string[]; rows: (string | number)[][]; } /** Action buttons block */ export interface ChatBlockActions { type: "actions"; buttons: Array<{ label: string; variant?: "primary" | "secondary" | "critical"; onClick: () => void; }>; } /** Choice/option selector — AI presents options, user picks one (or multiple) */ export interface ChatBlockChoice { type: "choice"; /** Unique ID for this choice block (used in onBlockEvent callback) */ id: string; title?: string; /** Allow multiple selections */ multiple?: boolean; options: Array<{ id: string; label: string; description?: string; status?: StatusLevel; disabled?: boolean; }>; /** Pre-selected option IDs */ selected?: string[]; /** Has the user already submitted their choice? */ submitted?: boolean; } /** Confirmation gate — requires explicit operator acknowledgment before proceeding */ export interface ChatBlockConfirm { type: "confirm"; /** Unique ID for this confirm block (used in onBlockEvent callback) */ id: string; status?: StatusLevel; title: string; detail?: string; confirmLabel?: string; cancelLabel?: string; /** Has the operator already responded? */ resolved?: "confirmed" | "cancelled"; } /** Command block — displays a command with copy-to-clipboard and optional execute */ export interface ChatBlockCommand { type: "command"; /** Unique ID for this command block (used in onBlockEvent callback) */ id?: string; language?: string; command: string; label?: string; /** Show an execute button (fires onBlockEvent with action:'execute') */ executable?: boolean; } /** Key-value metadata block — compact pairs for context/metadata */ export interface ChatBlockKV { type: "kv"; title?: string; pairs: Array<{ key: string; value: string | number; mono?: boolean; }>; } export type ChatBlock = ChatBlockAlert | ChatBlockTelemetry | ChatBlockProgress | ChatBlockTable | ChatBlockActions | ChatBlockChoice | ChatBlockConfirm | ChatBlockCommand | ChatBlockKV; /** Event emitted by interactive blocks (choice, confirm, command) */ export interface ChatBlockEvent { /** The block ID */ blockId: string; /** The message ID containing this block */ messageId: string; /** Action type */ action: "select" | "confirm" | "cancel" | "execute" | "copy"; /** Payload (selected option IDs for choice, command string for execute/copy) */ value?: string | string[]; } export interface ChatMessage { /** Unique message ID */ id: string; /** Message content (supports markdown-like formatting) */ content: string; /** Sender role */ role: "user" | "assistant" | "system"; /** Timestamp (ms) */ timestamp?: number; /** Message delivery status */ status?: "sending" | "sent" | "error"; /** Astro UX severity — colors the message accent (system messages default to this) */ severity?: StatusLevel; /** Structured blocks rendered below the text content */ blocks?: ChatBlock[]; /** Legacy: action buttons (prefer blocks with type:'actions') */ actions?: Array<{ label: string; onClick: () => void; }>; } export interface ChatPanelProps { /** Array of messages */ messages: ChatMessage[]; /** Send handler */ onSend: (text: string) => void; /** Is assistant currently generating */ loading?: boolean; /** Input placeholder */ placeholder?: string; /** Panel title */ title?: string; /** Subtitle (e.g. model name) */ subtitle?: string; /** Disable input */ disabled?: boolean; /** Show timestamps */ showTimestamps?: boolean; /** Height of the panel (default: 100%) */ height?: string | number; /** Clear handler (shows clear button when provided) */ onClear?: () => void; /** Handler for interactive block events (choice selection, confirm/cancel, command execute) */ onBlockEvent?: (event: ChatBlockEvent) => void; /** * Render a message's text yourself instead of using the built-in renderer. * * The built-in one covers `**bold**`, `` `code` `` and fenced code blocks. * Anything richer — lists, tables, links — needs a real markdown renderer, * which is a dependency this package does not carry. Supply one here to hand * that decision (and its bundle cost) to the host. * * Blocks are unaffected; they render as normal beneath the returned content. */ renderMessageContent?: (content: string, message: ChatMessage) => React.ReactNode; /** Custom style */ style?: React.CSSProperties; } /** * Supported response formats from the AI backend. * - 'json': Standard JSON (default). Most compatible. * - 'yaml': YAML compact format. ~30-40% fewer tokens than JSON for identical data. * - 'auto': Auto-detect (tries JSON first, then YAML, then plain text). */ export type ChatResponseFormat = "json" | "yaml" | "auto"; /** Shape of a parsed AI response (content + optional blocks + severity). */ export interface ChatResponsePayload { content: string; severity?: StatusLevel; blocks?: ChatBlock[]; } /** Options for creating a customised response parser. */ export interface ChatResponseParserOptions { /** * Custom YAML parser function. Receives raw YAML text, returns a parsed object. * If not provided, uses the built-in lightweight parser. * * For production, plug in `js-yaml` or `yaml`: * ```ts * import yaml from 'js-yaml'; * const parse = createChatResponseParser({ yamlParser: (s) => yaml.load(s) }); * ``` */ yamlParser?: (text: string) => unknown; /** Default format when not specified per-call. Defaults to `'auto'`. */ defaultFormat?: ChatResponseFormat; /** Custom message ID generator. Defaults to `crypto.randomUUID()`. */ idGenerator?: () => string; } /** * Parse an AI response string into a `ChatMessage`. * * Supports three strategies (controlled by `format`): * - **json**: Parse as JSON. Falls back to plain text on failure. * - **yaml**: Parse as YAML (built-in lightweight parser). ~30-40% fewer tokens. * - **auto** (default): Try JSON → YAML → plain text. * * All parsed blocks are sanitized to ensure required arrays exist, preventing * runtime crashes from malformed AI output. * * ## Why YAML? * For the same structured response, YAML uses ~30-40% fewer tokens: * ``` * JSON (~180 tokens): {"content":"Battery low.","blocks":[{"type":"alert",...}]} * YAML (~120 tokens): content: Battery low.\nblocks:\n - type: alert\n ... * ``` * * ## Tool/Function Calling (recommended for production) * Pass `CHAT_RESPONSE_TOOL_SCHEMA` in your API call. The model is constrained * at the decoding level — zero prompt tokens for the schema. * * ## Custom YAML parser * Use `createChatResponseParser({ yamlParser })` to plug in `js-yaml` or `yaml`. * * @example * ```ts * const msg = parseChatResponse(aiOutput); // auto-detect * const msg = parseChatResponse(aiOutput, 'yaml'); // force YAML * const msg = parseChatResponse(toolCallArgs, 'json'); // from function call * ``` */ export declare function parseChatResponse(raw: string, format?: ChatResponseFormat, options?: ChatResponseParserOptions): ChatMessage; /** * Create a pre-configured response parser. * * Use this to set global defaults (format, YAML parser, ID generator) so you * don't have to pass options on every call. * * @example * ```ts * import yaml from 'js-yaml'; * import { createChatResponseParser } from '@zendir/ui'; * * // Create once at app init * export const parseAI = createChatResponseParser({ * yamlParser: (s) => yaml.load(s), * defaultFormat: 'yaml', * }); * * // Use everywhere * const msg = parseAI(aiOutput); * ``` */ export declare function createChatResponseParser(options?: ChatResponseParserOptions): (raw: string, format?: ChatResponseFormat) => ChatMessage; /** * Lightweight types for MCP tool results. * These mirror the MCP specification without requiring the full SDK as a dependency. * @see https://modelcontextprotocol.io/specification/2025-11-25 */ /** A single content block in an MCP tool result. */ export interface McpToolContent { type: "text" | "image" | "resource"; text?: string; data?: string; mimeType?: string; resource?: { uri: string; text?: string; mimeType?: string; }; } /** MCP tool call result (matches `CallToolResult` from the spec). */ export interface McpToolResult { content: McpToolContent[]; isError?: boolean; _meta?: Record; } /** * Convert an MCP tool call result into a `ChatMessage` with structured blocks. * * Handles three patterns automatically: * 1. **Structured JSON/YAML** — Tool returns text matching `ChatResponsePayload` → parsed into typed blocks * 2. **Plain text** — Rendered as a simple assistant message with tool metadata * 3. **Error** — Rendered as a critical alert block * * This bridges the MCP protocol with ChatPanel's block rendering system, so any * MCP server tool can produce rich operator UIs without custom rendering code. * * @example * ```ts * import { parseMcpToolResult } from '@zendir/ui/react'; * * // With use-mcp hook * const { callTool } = useMcp({ url: 'https://ops-server.example.com/mcp' }); * const result = await callTool('get_satellite_health', { id: 'SAT-001' }); * const msg = parseMcpToolResult('get_satellite_health', result); * setMessages(prev => [...prev, msg]); * * // With @modelcontextprotocol/sdk * const result = await mcpClient.callTool({ name: 'get_satellite_health', arguments: { id: 'SAT-001' } }); * const msg = parseMcpToolResult('get_satellite_health', result); * ``` */ export declare function parseMcpToolResult(toolName: string, result: McpToolResult, options?: { idGenerator?: () => string; }): ChatMessage; /** * MCP server tool definition for the `respond_to_operator` tool. * * Register this on your MCP server to enable structured ChatPanel responses. * The schema is identical to `CHAT_RESPONSE_TOOL_SCHEMA` but formatted for the * MCP `server.tool()` registration API. * * Compatible with: `@modelcontextprotocol/sdk`, any MCP server implementation. * * @example * ```ts * import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; * import { CHAT_RESPONSE_MCP_TOOL } from '@zendir/ui'; * * const server = new McpServer({ name: 'ops-assistant', version: '1.0.0' }); * * server.tool( * CHAT_RESPONSE_MCP_TOOL.name, * CHAT_RESPONSE_MCP_TOOL.description, * CHAT_RESPONSE_MCP_TOOL.inputSchema, * async ({ content, severity, blocks }) => ({ * content: [{ type: 'text', text: JSON.stringify({ content, severity, blocks }) }], * }) * ); * ``` */ export declare const CHAT_RESPONSE_MCP_TOOL: { readonly name: "respond_to_operator"; readonly description: "Respond to the spacecraft operator with text content and optional structured UI blocks (alerts, telemetry, progress, tables, choices, confirmations, commands, key-value metadata). Use the Astro UX 6-level status system: normal, standby, caution, serious, critical, off."; readonly inputSchema: { readonly type: "object"; readonly required: readonly ["content"]; readonly properties: { readonly content: { readonly type: "string"; readonly description: "Plain-text or markdown response shown to the operator."; }; readonly severity: { readonly type: "string"; readonly enum: readonly ["normal", "standby", "caution", "serious", "critical", "off"]; }; readonly blocks: { readonly type: "array"; readonly description: "Structured UI blocks rendered below the text content."; readonly items: { readonly type: "object"; readonly required: readonly ["type"]; readonly properties: { readonly type: { readonly type: "string"; readonly enum: readonly ["alert", "telemetry", "progress", "table", "choice", "confirm", "command", "kv"]; }; readonly status: { readonly type: "string"; readonly enum: readonly ["normal", "standby", "caution", "serious", "critical", "off"]; }; readonly title: { readonly type: "string"; }; readonly detail: { readonly type: "string"; }; readonly label: { readonly type: "string"; }; readonly value: { readonly type: "number"; }; readonly id: { readonly type: "string"; }; readonly command: { readonly type: "string"; }; readonly executable: { readonly type: "boolean"; }; readonly items: { readonly type: "array"; readonly items: { readonly type: "object"; readonly properties: { readonly label: { readonly type: "string"; }; readonly value: {}; readonly unit: { readonly type: "string"; }; readonly status: { readonly type: "string"; }; }; }; }; readonly columns: { readonly type: "array"; readonly items: { readonly type: "string"; }; }; readonly rows: { readonly type: "array"; readonly items: { readonly type: "array"; }; }; readonly options: { readonly type: "array"; readonly items: { readonly type: "object"; readonly properties: { readonly id: { readonly type: "string"; }; readonly label: { readonly type: "string"; }; readonly description: { readonly type: "string"; }; readonly status: { readonly type: "string"; }; }; }; }; readonly pairs: { readonly type: "array"; readonly items: { readonly type: "object"; readonly properties: { readonly key: { readonly type: "string"; }; readonly value: {}; readonly mono: { readonly type: "boolean"; }; }; }; }; }; }; }; }; }; }; /** * OpenAI-compatible function/tool schema for structured chat responses. * * Pass this as a tool definition in your API call. The model is constrained * to emit valid JSON matching this schema — no prompt engineering required. * * Works with: OpenAI (Structured Outputs / function calling), Anthropic (Tool Use), * Google Gemini (Function Calling), Mistral, Cohere, and any OpenAI-compatible API. * * @example * ```ts * // OpenAI * tools: [{ type: 'function', function: CHAT_RESPONSE_TOOL_SCHEMA }] * * // Anthropic * tools: [{ name: CHAT_RESPONSE_TOOL_SCHEMA.name, * description: CHAT_RESPONSE_TOOL_SCHEMA.description, * input_schema: CHAT_RESPONSE_TOOL_SCHEMA.parameters }] * * // Google Gemini * tools: [{ functionDeclarations: [CHAT_RESPONSE_TOOL_SCHEMA] }] * ``` */ export declare const CHAT_RESPONSE_TOOL_SCHEMA: { readonly name: "respond_to_operator"; readonly description: "Respond to the spacecraft operator with text content and optional structured UI blocks (alerts, telemetry, progress, tables, choices, confirmations, commands, key-value metadata). Use the Astro UX 6-level status system: normal, standby, caution, serious, critical, off."; readonly parameters: { readonly type: "object"; readonly required: readonly ["content"]; readonly properties: { readonly content: { readonly type: "string"; readonly description: "Plain-text or markdown response shown to the operator."; }; readonly severity: { readonly type: "string"; readonly enum: readonly ["normal", "standby", "caution", "serious", "critical", "off"]; readonly description: "Overall message severity. Drives the accent color and icon."; }; readonly blocks: { readonly type: "array"; readonly description: "Structured UI blocks rendered below the text content."; readonly items: { readonly type: "object"; readonly required: readonly ["type"]; readonly properties: { readonly type: { readonly type: "string"; readonly enum: readonly ["alert", "telemetry", "progress", "table", "choice", "confirm", "command", "kv"]; }; readonly status: { readonly type: "string"; readonly enum: readonly ["normal", "standby", "caution", "serious", "critical", "off"]; }; readonly title: { readonly type: "string"; }; readonly detail: { readonly type: "string"; }; readonly items: { readonly type: "array"; readonly items: { readonly type: "object"; readonly properties: { readonly label: { readonly type: "string"; }; readonly value: {}; readonly unit: { readonly type: "string"; }; readonly status: { readonly type: "string"; readonly enum: readonly ["normal", "standby", "caution", "serious", "critical", "off"]; }; }; }; }; readonly label: { readonly type: "string"; }; readonly value: { readonly type: "number"; }; readonly max: { readonly type: "number"; }; readonly columns: { readonly type: "array"; readonly items: { readonly type: "string"; }; }; readonly rows: { readonly type: "array"; readonly items: { readonly type: "array"; }; }; readonly id: { readonly type: "string"; }; readonly multiple: { readonly type: "boolean"; }; readonly options: { readonly type: "array"; readonly items: { readonly type: "object"; readonly properties: { readonly id: { readonly type: "string"; }; readonly label: { readonly type: "string"; }; readonly description: { readonly type: "string"; }; readonly status: { readonly type: "string"; readonly enum: readonly ["normal", "standby", "caution", "serious", "critical", "off"]; }; readonly disabled: { readonly type: "boolean"; }; }; }; }; readonly confirmLabel: { readonly type: "string"; }; readonly cancelLabel: { readonly type: "string"; }; readonly language: { readonly type: "string"; }; readonly command: { readonly type: "string"; }; readonly executable: { readonly type: "boolean"; }; readonly pairs: { readonly type: "array"; readonly items: { readonly type: "object"; readonly properties: { readonly key: { readonly type: "string"; }; readonly value: {}; readonly mono: { readonly type: "boolean"; }; }; }; }; }; }; }; }; }; }; /** * JSON system prompt snippet — instructs the LLM to respond with JSON. * Include in your system prompt for the simplest integration path. */ export declare const CHAT_RESPONSE_JSON_PROMPT: "When reporting telemetry, status, or alerts, respond with a JSON object:\n\n{\n \"content\": \"Plain text explanation (supports **markdown**).\",\n \"severity\": \"normal\" | \"standby\" | \"caution\" | \"serious\" | \"critical\" | \"off\",\n \"blocks\": [\n { \"type\": \"alert\", \"status\": \"caution\", \"title\": \"Alert Title\", \"detail\": \"Description\" },\n { \"type\": \"telemetry\", \"title\": \"Subsystem\", \"items\": [{ \"label\": \"Temp\", \"value\": 72, \"unit\": \"\u00B0C\", \"status\": \"normal\" }] },\n { \"type\": \"progress\", \"label\": \"Operation\", \"value\": 75, \"max\": 100, \"status\": \"normal\" },\n { \"type\": \"table\", \"title\": \"Data\", \"columns\": [\"Col1\", \"Col2\"], \"rows\": [[\"val1\", \"val2\"]] },\n { \"type\": \"choice\", \"id\": \"unique-id\", \"title\": \"Pick one\", \"options\": [{ \"id\": \"opt1\", \"label\": \"Option 1\" }] },\n { \"type\": \"confirm\", \"id\": \"unique-id\", \"status\": \"caution\", \"title\": \"Confirm action\" },\n { \"type\": \"command\", \"id\": \"cmd-id\", \"command\": \"CMD SAT-001 EXECUTE\", \"executable\": true },\n { \"type\": \"kv\", \"title\": \"Context\", \"pairs\": [{ \"key\": \"Spacecraft\", \"value\": \"SAT-001\" }] }\n ]\n}\n\nStatus levels (Astro UX): normal (nominal), standby (idle), caution (approaching limit), serious (at limit), critical (exceeding limit), off (no data).\nAlways include \"content\". Only include \"blocks\" when structured data helps the operator."; /** * YAML system prompt snippet — instructs the LLM to respond in compact YAML * instead of JSON. ~30-40% fewer output tokens for the same data. */ export declare const CHAT_RESPONSE_YAML_PROMPT: "Respond in YAML format (not JSON). Use this structure:\n\ncontent: Your plain-text or markdown response here.\nseverity: normal\nblocks:\n - type: alert\n status: caution\n title: Alert Title\n detail: Optional detail\n - type: telemetry\n title: Subsystem\n items:\n - label: Parameter\n value: 42.5\n unit: \"\u00B0C\"\n status: normal\n - type: progress\n label: Operation\n value: 75\n max: 100\n status: normal\n - type: choice\n id: unique-id\n title: Pick one\n options:\n - id: opt1\n label: Option 1\n status: normal\n - type: command\n id: cmd-id\n language: CCSDS-TC\n command: CMD SAT-001 EXECUTE\n executable: true\n - type: kv\n title: Context\n pairs:\n - key: Spacecraft\n value: SAT-001\n\nStatus levels (Astro UX): normal, standby, caution, serious, critical, off.\nAlways include \"content\". Only include \"blocks\" when structured data helps the operator."; /** * Astro UX status rules snippet — append to any system prompt (JSON or YAML). * Gives the LLM concrete thresholds for assigning status levels. */ export declare const CHAT_STATUS_RULES_PROMPT: "Assign status levels using these thresholds:\n- Battery: normal >50%, standby 30-50%, caution 20-30%, serious 10-20%, critical <10%\n- Temperature: normal <70\u00B0C, caution 70-80\u00B0C, serious 80-85\u00B0C, critical >85\u00B0C\n- Signal: normal >-80 dBm, caution -80 to -90, serious -90 to -95, critical <-95 dBm\n- Memory/Storage: normal <70%, caution 70-85%, serious 85-95%, critical >95%\n\nGeneral rules:\n- \"normal\" \u2192 Operating within nominal parameters\n- \"standby\" \u2192 Idle, waiting, or scheduled\n- \"caution\" \u2192 Approaching a limit\n- \"serious\" \u2192 At or exceeding a soft limit\n- \"critical\" \u2192 Exceeding hard limit, loss of signal, or failure\n- \"off\" \u2192 Powered down, no data"; export declare const ChatPanel: React.NamedExoticComponent; export default ChatPanel;