wip: more rework and docs

This commit is contained in:
2026-02-26 11:01:17 +01:00
parent affd62ca79
commit 00a9d22a36
18 changed files with 149 additions and 226 deletions

33
API.md
View File

@@ -1,6 +1,6 @@
# API Documentation # API Documentation
Contract version: 1.5.0 Contract version: 1.6.0
This reference documents all Python runtime API calls available through `bds_api` in embedded Pyodide. This reference documents all Python runtime API calls available through `bds_api` in embedded Pyodide.
@@ -4050,37 +4050,6 @@ Stored API key state for chat provider.
[↑ Back to Table of contents](#table-of-contents) [↑ Back to Table of contents](#table-of-contents)
### ProtocolNeedsInputField
A required clarification input field used for needsInput prompts.
**Fields**
- key (`string`, required): Stable field key used in submitted values.
- label (`string`, required): User-facing field label.
- inputType (`'text' | 'textarea' | 'select' | 'checkbox' | 'date' | 'number'`, required): Rendered input control type.
- required (`boolean`, optional): Whether user input is required.
- options (`Array<{ label: string; value: string }>`, optional): Selectable options for select controls.
- placeholder (`string`, optional): Optional placeholder text for text-like controls.
- defaultValue (`string | number | boolean`, optional): Default field value shown in UI.
[↑ Back to Table of contents](#table-of-contents)
### ProtocolAction
A declarative assistant action exposed to the UI runtime.
**Fields**
- id (`string`, required): Stable action id within a response envelope.
- action (`string`, required): Action name to dispatch in renderer.
- label (`string`, optional): Optional user-facing action label.
- payload (`Record<string, unknown>`, optional): Optional action payload arguments.
- policy (`'silent' | 'confirm' | 'danger'`, required): Action confirmation policy level.
- requiresConfirmation (`boolean`, required): Whether confirmation is required before dispatch.
[↑ Back to Table of contents](#table-of-contents)
--- ---
Generated from contract at 2026-02-25T00:00:00.000Z. Generated from contract at 2026-02-25T00:00:00.000Z.

View File

@@ -11,7 +11,7 @@
- [Working with media](#working-with-media) - [Working with media](#working-with-media)
- [Using macros](#using-macros) - [Using macros](#using-macros)
- [Using scripting (early access)](#using-scripting-early-access) - [Using scripting (early access)](#using-scripting-early-access)
- [Using assistant panel widgets](#using-assistant-panel-widgets) - [Using the AI assistant](#using-the-ai-assistant)
- [Organizing with tags](#organizing-with-tags) - [Organizing with tags](#organizing-with-tags)
- [Importing from WordPress (WXR)](#importing-from-wordpress-wxr) - [Importing from WordPress (WXR)](#importing-from-wordpress-wxr)
- [Using Git (Source Control)](#using-git-source-control) - [Using Git (Source Control)](#using-git-source-control)
@@ -256,160 +256,60 @@ Notes:
--- ---
## Using assistant panel widgets ## Using the AI assistant
The assistant sidebar can render structured panel widgets when the AI response includes a valid JSON panel spec. This is useful when you want the assistant to return actionable UI instead of plain text only. The AI assistant is built into bDS to help you manage your blog through natural conversation. You can ask it to search posts, analyze your content, update metadata, and visualize data. Instead of returning only plain text, the assistant can present results as rich interactive elements such as charts, tables, forms, and more.
Use this envelope: The assistant works entirely with your local blog content. It does not have access to the internet or external services. When you ask a question, it uses your posts, media, tags, and categories to find answers and present them in the most useful format. In most cases the assistant automatically picks the right visualization for your request, but you can also ask for a specific format explicitly.
```json ### Charts
{
"specVersion": "1",
"elements": []
}
```
### Supported widget types The assistant can display bar, line, and pie charts to help you spot patterns and trends in your blog data. Charts include a title, labeled data points, and a visual representation that makes it easy to compare values at a glance.
- `text` **Try asking:** "Show me a chart of posts published per month this year"
- `metric`
- `list`
- `table`
- `action`
- `chart`
- `input`
- `form`
- `datePicker`
- `card`
- `image`
- `tabs`
### Example snippets ### Tables
```json When you need to compare posts side by side or see structured information, the assistant can render a table with columns and rows. Tables are useful for listings, comparisons, and any data that benefits from a grid layout.
{ "type": "text", "text": "Review complete." }
```
```json **Try asking:** "Compare my last 10 posts showing title, status, and word count"
{ "type": "metric", "label": "Draft posts", "value": "12" }
```
```json ### Cards
{ "type": "list", "title": "Next steps", "items": ["Refine title", "Add tags"] }
```
```json Cards present a focused summary with a title, body text, and optional action buttons. The assistant uses cards when highlighting a specific item, making a recommendation, or presenting a result that you might want to act on.
{
"type": "table",
"columns": ["Post", "Status"],
"rows": [["Roadmap", "Draft"], ["Release", "Published"]]
}
```
```json **Try asking:** "Give me a summary card for my most recent draft post"
{
"type": "action",
"label": "Open tags",
"action": "switchView",
"payload": { "view": "tags" }
}
```
```json ### Metrics
{
"type": "chart",
"chartType": "bar",
"title": "Posts by month",
"series": [
{ "label": "Jan", "value": 10 },
{ "label": "Feb", "value": 14 }
]
}
```
```json A metric is a single prominent number or value with a label. The assistant uses metrics when the answer to your question is one key figure, such as a count, a status, or a statistic.
{
"type": "input",
"key": "query",
"label": "Search",
"inputType": "text",
"placeholder": "Find post...",
"submitLabel": "Run",
"action": "openChat"
}
```
```json **Try asking:** "How many draft posts do I have?"
{
"type": "form",
"formId": "meta-form",
"title": "Update metadata",
"submitLabel": "Apply",
"action": "openSettings",
"fields": [
{ "key": "title", "label": "Title", "inputType": "text" },
{ "key": "isDraft", "label": "Draft", "inputType": "checkbox" }
]
}
```
```json ### Lists
{
"type": "datePicker",
"key": "publishDate",
"label": "Publish date",
"submitLabel": "Set",
"action": "openSettings"
}
```
```json Lists display items as a simple bulleted enumeration. They work well for tag listings, next steps, checklists, and any result that is naturally a sequence of items.
{
"type": "card",
"title": "Suggestion",
"subtitle": "Editorial",
"body": "Add one category and two tags.",
"actions": [
{ "label": "Open tags", "action": "switchView", "payload": { "view": "tags" } }
]
}
```
```json **Try asking:** "List all tags that are used by fewer than 3 posts"
{
"type": "image",
"src": "https://example.com/preview.png",
"alt": "Generated preview",
"caption": "Current preview snapshot"
}
```
```json ### Forms
{
"type": "tabs",
"defaultTabId": "summary",
"tabs": [
{
"id": "summary",
"label": "Summary",
"elements": [{ "type": "text", "text": "Short summary" }]
},
{
"id": "actions",
"label": "Actions",
"elements": [
{ "type": "action", "label": "Open settings", "action": "openSettings" }
]
}
]
}
```
### Notes When the assistant needs structured input from you, it can display an interactive form with text fields, checkboxes, dropdowns, and date pickers. Forms are typically used for metadata updates, multi-field edits, and configuration tasks where typing everything into a single message would be awkward.
- `tabs` are panel-local UI tabs inside one assistant response; they are not editor tabs. **Try asking:** "Help me update the metadata for my post about React"
- Unknown or invalid widget payloads are ignored by the parser.
- Actions are restricted to supported safe action names in the app. ### Tabs
Tabs let the assistant organize multiple views into a single switchable interface. Each tab can contain any combination of text, charts, tables, metrics, and lists. Tabs are especially useful for multi-dimensional comparisons where you want to explore different slices of data without scrolling through a long response.
**Try asking:** "Show post statistics by year, with each year as a tab containing a chart of monthly post counts"
### Key takeaways
- The assistant picks the right visualization automatically based on your question.
- You can ask for a specific format by mentioning it in your prompt ("show as a chart", "put it in a table").
- Tabs can contain charts, tables, and other elements for rich multi-view displays.
- The assistant can only access your local blog content.
[↑ Back to In this article](#in-this-article) [↑ Back to In this article](#in-this-article)

View File

@@ -328,7 +328,7 @@ Available UI Render Tools (use these to show rich interactive elements):
- render_card: Show an information card with title, body, and action buttons. - render_card: Show an information card with title, body, and action buttons.
- render_metric: Show a single KPI or statistic prominently. - render_metric: Show a single KPI or statistic prominently.
- render_list: Show a bulleted list of items. - render_list: Show a bulleted list of items.
- render_tabs: Organize information into switchable tabs. - render_tabs: Organize information into switchable tabs. Tab content supports all content types: text, metrics, lists, charts, and tables.
When answering questions: When answering questions:
1. USE THE TOOLS to find information. Never make up data about posts or media. 1. USE THE TOOLS to find information. Never make up data about posts or media.
@@ -338,7 +338,8 @@ When answering questions:
5. When asked to describe or analyze an image, use the view_image tool to see the actual image content. 5. When asked to describe or analyze an image, use the view_image tool to see the actual image content.
6. When presenting data, statistics, or comparisons, prefer using render tools (render_chart, render_table, render_metric) to show rich interactive UI instead of plain text. 6. When presenting data, statistics, or comparisons, prefer using render tools (render_chart, render_table, render_metric) to show rich interactive UI instead of plain text.
7. When you need user input for a multi-field operation, use render_form to present a structured form. 7. When you need user input for a multi-field operation, use render_form to present a structured form.
8. Use render_card with action buttons when presenting items the user might want to navigate to (e.g., posts, media).`; 8. Use render_card with action buttons when presenting items the user might want to navigate to (e.g., posts, media).
9. When comparing data across multiple dimensions (e.g., statistics per year), use render_tabs with embedded charts or tables in each tab.`;
} }
/** /**

View File

@@ -1018,7 +1018,7 @@ export class OpenCodeManager {
}, },
{ {
name: 'render_tabs', name: 'render_tabs',
description: 'Render a tabbed interface in the chat UI. Use this when you want to organize information into multiple tabs that the user can switch between.', description: 'Render a tabbed interface in the chat UI. Use this when you want to organize information into multiple tabs that the user can switch between. Each tab can contain any combination of text, metrics, lists, charts, and tables.',
input_schema: { input_schema: {
type: 'object', type: 'object',
properties: { properties: {
@@ -1033,7 +1033,28 @@ export class OpenCodeManager {
items: { items: {
type: 'object', type: 'object',
properties: { properties: {
type: { type: 'string', enum: ['text', 'metric', 'list'], description: 'Content type' }, type: { type: 'string', enum: ['text', 'metric', 'list', 'chart', 'table'], description: 'Content type' },
text: { type: 'string', description: 'Text content (for type text)' },
label: { type: 'string', description: 'Label (for type metric)' },
value: { type: 'string', description: 'Display value (for type metric)' },
title: { type: 'string', description: 'Title (for type list, chart, or table)' },
items: { type: 'array', items: { type: 'string' }, description: 'Items (for type list)' },
chartType: { type: 'string', enum: ['bar', 'line', 'pie'], description: 'Chart type (for type chart)' },
series: {
type: 'array',
items: {
type: 'object',
properties: { label: { type: 'string' }, value: { type: 'number' } },
required: ['label', 'value'],
},
description: 'Data series (for type chart)',
},
columns: { type: 'array', items: { type: 'string' }, description: 'Column headers (for type table)' },
rows: {
type: 'array',
items: { type: 'array', items: { type: 'string' } },
description: 'Table rows (for type table)',
},
}, },
required: ['type'], required: ['type'],
}, },

View File

@@ -17,7 +17,7 @@ interface SeriesEntry {
export const A2UIChart: React.FC<A2UIComponentProps> = ({ component }) => { export const A2UIChart: React.FC<A2UIComponentProps> = ({ component }) => {
const chartType = String(component.properties.chartType ?? 'bar'); const chartType = String(component.properties.chartType ?? 'bar');
const title = component.properties.title as string | undefined; const title = component.properties.title as string | undefined;
const series = (component.boundValue as SeriesEntry[]) ?? []; const series = (component.boundValue as SeriesEntry[]) ?? (component.properties.series as SeriesEntry[]) ?? [];
const maxValue = Math.max(...series.map((entry) => entry.value), 0); const maxValue = Math.max(...series.map((entry) => entry.value), 0);
return ( return (

View File

@@ -11,7 +11,7 @@ interface A2UIComponentProps {
export const A2UIList: React.FC<A2UIComponentProps> = ({ component }) => { export const A2UIList: React.FC<A2UIComponentProps> = ({ component }) => {
const title = component.properties.title as string | undefined; const title = component.properties.title as string | undefined;
const items = (component.boundValue as string[]) ?? []; const items = (component.boundValue as string[]) ?? (component.properties.items as string[]) ?? [];
return ( return (
<div> <div>

View File

@@ -11,7 +11,7 @@ interface A2UIComponentProps {
export const A2UITable: React.FC<A2UIComponentProps> = ({ component }) => { export const A2UITable: React.FC<A2UIComponentProps> = ({ component }) => {
const columns = (component.properties.columns as string[]) ?? []; const columns = (component.properties.columns as string[]) ?? [];
const rows = (component.boundValue as string[][]) ?? []; const rows = (component.boundValue as string[][]) ?? (component.properties.rows as string[][]) ?? [];
const title = component.properties.title as string | undefined; const title = component.properties.title as string | undefined;
return ( return (

View File

@@ -250,7 +250,7 @@ export const AssistantSidebar: React.FC = () => {
{actionError && <p className="assistant-sidebar-error chat-surface-error">{actionError}</p>} {actionError && <p className="assistant-sidebar-error chat-surface-error">{actionError}</p>}
{surfaceMode.showWelcomeTips && messages.length === 0 && !isStreaming && ( {surfaceMode.showWelcomeTips && messages.filter(m => m.role !== 'system' && m.role !== 'tool').length === 0 && !isStreaming && (
<div className="assistant-sidebar-raw-message chat-surface-section"> <div className="assistant-sidebar-raw-message chat-surface-section">
{tr('chat.welcomeDescription')} {tr('chat.welcomeDescription')}
</div> </div>

View File

@@ -98,6 +98,8 @@
justify-content: center; justify-content: center;
text-align: center; text-align: center;
padding: 32px; padding: 32px;
min-height: 100%;
box-sizing: border-box;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
} }

View File

@@ -344,17 +344,17 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({ conversationId }) => {
</div> </div>
<div className="chat-messages chat-surface-scroll"> <div className="chat-messages chat-surface-scroll">
{surfaceMode.showWelcomeTips && messages.length === 0 && !isStreaming && ( {surfaceMode.showWelcomeTips && messages.filter(m => m.role !== 'system' && m.role !== 'tool').length === 0 && !isStreaming && (
<div className="chat-welcome"> <div className="chat-welcome">
<div className="chat-welcome-icon">{'\u{1F916}'}</div> <div className="chat-welcome-icon">{'\u{1F916}'}</div>
<h2>{tr('chat.welcomeTitle')}</h2> <h2>{tr('chat.welcomeTitle')}</h2>
<p>{tr('chat.welcomeDescription')}</p> <p>{tr('chat.welcomeDescription')}</p>
<ul> <ul>
<li>{tr('chat.welcomeTipSearch')}</li> <li>{tr('chat.welcomeTipSearch')}</li>
<li>{tr('chat.welcomeTipDetails')}</li> <li>{tr('chat.welcomeTipChart')}</li>
<li>{tr('chat.welcomeTipTags')}</li> <li>{tr('chat.welcomeTipTable')}</li>
<li>{tr('chat.welcomeTipMetadata')}</li> <li>{tr('chat.welcomeTipMetadata')}</li>
<li>{tr('chat.welcomeTipImages')}</li> <li>{tr('chat.welcomeTipTabs')}</li>
</ul> </ul>
</div> </div>
)} )}

View File

@@ -196,12 +196,12 @@
"chat.apiKeyValidationFailed": "API-Schlüssel konnte nicht validiert werden.", "chat.apiKeyValidationFailed": "API-Schlüssel konnte nicht validiert werden.",
"chat.newChat": "Neuer Chat", "chat.newChat": "Neuer Chat",
"chat.welcomeTitle": "Willkommen beim KI-Assistenten", "chat.welcomeTitle": "Willkommen beim KI-Assistenten",
"chat.welcomeDescription": "Ich kann dir helfen, deine Beiträge und Medien zu verwalten. Frag mich zum Beispiel:", "chat.welcomeDescription": "Ich kann dir helfen, deinen Blog mit anschaulichen Darstellungen zu verwalten. Frag mich zum Beispiel:",
"chat.welcomeTipSearch": "Nach Beiträgen zu einem bestimmten Thema suchen", "chat.welcomeTipSearch": "Nach Beiträgen zu einem bestimmten Thema suchen",
"chat.welcomeTipDetails": "Details zu einem bestimmten Beitrag anzeigen", "chat.welcomeTipChart": "Ein Diagramm der pro Monat veröffentlichten Beiträge anzeigen",
"chat.welcomeTipTags": "Alle Tags oder Kategorien in deinem Blog auflisten", "chat.welcomeTipTable": "Meine letzten Beiträge in einer Tabelle vergleichen",
"chat.welcomeTipMetadata": "Metadaten für Beiträge oder Medien aktualisieren", "chat.welcomeTipMetadata": "Metadaten für Beiträge oder Medien aktualisieren",
"chat.welcomeTipImages": "Alle Bilder in deiner Mediathek auflisten", "chat.welcomeTipTabs": "Beitragsstatistiken nach Jahr in Tabs mit Diagrammen anzeigen",
"chat.role.you": "Du", "chat.role.you": "Du",
"chat.role.assistant": "Assistent", "chat.role.assistant": "Assistent",
"chat.stop": "Stopp", "chat.stop": "Stopp",

View File

@@ -196,12 +196,12 @@
"chat.apiKeyValidationFailed": "Failed to validate API key.", "chat.apiKeyValidationFailed": "Failed to validate API key.",
"chat.newChat": "New Chat", "chat.newChat": "New Chat",
"chat.welcomeTitle": "Welcome to the AI Assistant", "chat.welcomeTitle": "Welcome to the AI Assistant",
"chat.welcomeDescription": "I can help you manage your posts and media. Try asking me to:", "chat.welcomeDescription": "I can help you manage your blog with rich visualizations. Try asking me to:",
"chat.welcomeTipSearch": "Search for posts about a specific topic", "chat.welcomeTipSearch": "Search for posts about a specific topic",
"chat.welcomeTipDetails": "Get details about a specific post", "chat.welcomeTipChart": "Show a chart of posts published per month",
"chat.welcomeTipTags": "List all tags or categories in your blog", "chat.welcomeTipTable": "Compare my recent posts in a table",
"chat.welcomeTipMetadata": "Update metadata for posts or media", "chat.welcomeTipMetadata": "Update metadata for posts or media",
"chat.welcomeTipImages": "List all images in your media library", "chat.welcomeTipTabs": "Show post statistics by year in tabs with charts",
"chat.role.you": "You", "chat.role.you": "You",
"chat.role.assistant": "Assistant", "chat.role.assistant": "Assistant",
"chat.stop": "Stop", "chat.stop": "Stop",

View File

@@ -196,12 +196,12 @@
"chat.apiKeyValidationFailed": "No se pudo validar la clave API.", "chat.apiKeyValidationFailed": "No se pudo validar la clave API.",
"chat.newChat": "Nuevo chat", "chat.newChat": "Nuevo chat",
"chat.welcomeTitle": "Bienvenido al asistente de IA", "chat.welcomeTitle": "Bienvenido al asistente de IA",
"chat.welcomeDescription": "Puedo ayudarte a gestionar tus entradas y medios. Prueba a pedirme que:", "chat.welcomeDescription": "Puedo ayudarte a gestionar tu blog con visualizaciones interactivas. Prueba a pedirme que:",
"chat.welcomeTipSearch": "Busque entradas sobre un tema específico", "chat.welcomeTipSearch": "Busque entradas sobre un tema específico",
"chat.welcomeTipDetails": "Muestre detalles de una entrada específica", "chat.welcomeTipChart": "Muestre un gráfico de entradas publicadas por mes",
"chat.welcomeTipTags": "Lista todas las etiquetas o categorías de tu blog", "chat.welcomeTipTable": "Compare mis entradas recientes en una tabla",
"chat.welcomeTipMetadata": "Actualice metadatos de entradas o medios", "chat.welcomeTipMetadata": "Actualice metadatos de entradas o medios",
"chat.welcomeTipImages": "Liste todas las imágenes de tu biblioteca de medios", "chat.welcomeTipTabs": "Muestre estadísticas por año en pestañas con gráficos",
"chat.role.you": "Tú", "chat.role.you": "Tú",
"chat.role.assistant": "Asistente", "chat.role.assistant": "Asistente",
"chat.stop": "Detener", "chat.stop": "Detener",

View File

@@ -196,12 +196,12 @@
"chat.apiKeyValidationFailed": "Impossible de valider la clé API.", "chat.apiKeyValidationFailed": "Impossible de valider la clé API.",
"chat.newChat": "Nouveau chat", "chat.newChat": "Nouveau chat",
"chat.welcomeTitle": "Bienvenue dans lassistant IA", "chat.welcomeTitle": "Bienvenue dans lassistant IA",
"chat.welcomeDescription": "Je peux vous aider à gérer vos articles et médias. Essayez par exemple :", "chat.welcomeDescription": "Je peux vous aider à gérer votre blog avec des visualisations riches. Essayez par exemple :",
"chat.welcomeTipSearch": "Rechercher des articles sur un sujet précis", "chat.welcomeTipSearch": "Rechercher des articles sur un sujet précis",
"chat.welcomeTipDetails": "Afficher les détails dun article précis", "chat.welcomeTipChart": "Afficher un graphique des articles publiés par mois",
"chat.welcomeTipTags": "Lister toutes les étiquettes ou catégories de votre blog", "chat.welcomeTipTable": "Comparer mes derniers articles dans un tableau",
"chat.welcomeTipMetadata": "Mettre à jour les métadonnées des articles ou médias", "chat.welcomeTipMetadata": "Mettre à jour les métadonnées des articles ou médias",
"chat.welcomeTipImages": "Lister toutes les images de votre bibliothèque média", "chat.welcomeTipTabs": "Afficher les statistiques par année dans des onglets avec graphiques",
"chat.role.you": "Vous", "chat.role.you": "Vous",
"chat.role.assistant": "Assistant IA", "chat.role.assistant": "Assistant IA",
"chat.stop": "Arrêter", "chat.stop": "Arrêter",

View File

@@ -196,12 +196,12 @@
"chat.apiKeyValidationFailed": "Impossibile convalidare la chiave API.", "chat.apiKeyValidationFailed": "Impossibile convalidare la chiave API.",
"chat.newChat": "Nuova chat", "chat.newChat": "Nuova chat",
"chat.welcomeTitle": "Benvenuto nellassistente IA", "chat.welcomeTitle": "Benvenuto nellassistente IA",
"chat.welcomeDescription": "Posso aiutarti a gestire post e media. Prova a chiedermi di:", "chat.welcomeDescription": "Posso aiutarti a gestire il tuo blog con visualizzazioni interattive. Prova a chiedermi di:",
"chat.welcomeTipSearch": "Cercare post su un argomento specifico", "chat.welcomeTipSearch": "Cercare post su un argomento specifico",
"chat.welcomeTipDetails": "Ottieni dettagli su un post specifico", "chat.welcomeTipChart": "Mostrare un grafico dei post pubblicati per mese",
"chat.welcomeTipTags": "Elenca tutti i tag o le categorie del tuo blog", "chat.welcomeTipTable": "Confrontare i miei post recenti in una tabella",
"chat.welcomeTipMetadata": "Aggiornare i metadati di post o media", "chat.welcomeTipMetadata": "Aggiornare i metadati di post o media",
"chat.welcomeTipImages": "Elenca tutte le immagini nella tua libreria media", "chat.welcomeTipTabs": "Mostrare statistiche per anno in schede con grafici",
"chat.role.you": "Tu", "chat.role.you": "Tu",
"chat.role.assistant": "Assistente", "chat.role.assistant": "Assistente",
"chat.stop": "Ferma", "chat.stop": "Ferma",

View File

@@ -359,35 +359,10 @@ const DATA_STRUCTURES_V1: PythonApiDataStructureContractV1[] = [
{ name: 'maskedKey', type: 'string', required: true, description: 'Masked key representation for UI display.' }, { name: 'maskedKey', type: 'string', required: true, description: 'Masked key representation for UI display.' },
], ],
}, },
{
name: 'ProtocolNeedsInputField',
description: 'A required clarification input field used for needsInput prompts.',
fields: [
{ name: 'key', type: 'string', required: true, description: 'Stable field key used in submitted values.' },
{ name: 'label', type: 'string', required: true, description: 'User-facing field label.' },
{ name: 'inputType', type: "'text' | 'textarea' | 'select' | 'checkbox' | 'date' | 'number'", required: true, description: 'Rendered input control type.' },
{ name: 'required', type: 'boolean', required: false, description: 'Whether user input is required.' },
{ name: 'options', type: 'Array<{ label: string; value: string }>', required: false, description: 'Selectable options for select controls.' },
{ name: 'placeholder', type: 'string', required: false, description: 'Optional placeholder text for text-like controls.' },
{ name: 'defaultValue', type: 'string | number | boolean', required: false, description: 'Default field value shown in UI.' },
],
},
{
name: 'ProtocolAction',
description: 'A declarative assistant action exposed to the UI runtime.',
fields: [
{ name: 'id', type: 'string', required: true, description: 'Stable action id within a response envelope.' },
{ name: 'action', type: 'string', required: true, description: 'Action name to dispatch in renderer.' },
{ name: 'label', type: 'string', required: false, description: 'Optional user-facing action label.' },
{ name: 'payload', type: 'Record<string, unknown>', required: false, description: 'Optional action payload arguments.' },
{ name: 'policy', type: "'silent' | 'confirm' | 'danger'", required: true, description: 'Action confirmation policy level.' },
{ name: 'requiresConfirmation', type: 'boolean', required: true, description: 'Whether confirmation is required before dispatch.' },
],
},
]; ];
export const BDS_PYTHON_API_CONTRACT_V1: PythonApiContractV1 = { export const BDS_PYTHON_API_CONTRACT_V1: PythonApiContractV1 = {
version: '1.5.0', version: '1.6.0',
generatedAt: '2026-02-25T00:00:00.000Z', generatedAt: '2026-02-25T00:00:00.000Z',
methods: METHODS_V1, methods: METHODS_V1,
dataStructures: DATA_STRUCTURES_V1, dataStructures: DATA_STRUCTURES_V1,

View File

@@ -259,5 +259,60 @@ describe('A2UI generator', () => {
expect(tabsComponent!.children).toHaveLength(2); expect(tabsComponent!.children).toHaveLength(2);
expect(tabsComponent!.properties.tabLabels).toEqual(['Overview', 'Details']); expect(tabsComponent!.properties.tabLabels).toEqual(['Overview', 'Details']);
}); });
it('creates chart components inside tabs with series in properties', () => {
const messages = generateTabs('conv-1', {
tabs: [
{
label: 'Stats',
content: [{
type: 'chart',
chartType: 'bar',
title: 'Monthly Posts',
series: [{ label: 'Jan', value: 5 }, { label: 'Feb', value: 8 }],
}],
},
],
});
expect(messages).toHaveLength(2);
const updateMsg = messages[1] as Extract<A2UIServerMessage, { type: 'updateComponents' }>;
const chartComponent = updateMsg.components.find((c) => c.type === 'chart');
expect(chartComponent).toBeDefined();
expect(chartComponent!.properties.chartType).toBe('bar');
expect(chartComponent!.properties.title).toBe('Monthly Posts');
expect(chartComponent!.properties.series).toEqual([
{ label: 'Jan', value: 5 },
{ label: 'Feb', value: 8 },
]);
});
it('creates table components inside tabs with rows in properties', () => {
const messages = generateTabs('conv-1', {
tabs: [
{
label: 'Data',
content: [{
type: 'table',
title: 'Recent Posts',
columns: ['Title', 'Status'],
rows: [['Hello', 'published'], ['Draft', 'draft']],
}],
},
],
});
expect(messages).toHaveLength(2);
const updateMsg = messages[1] as Extract<A2UIServerMessage, { type: 'updateComponents' }>;
const tableComponent = updateMsg.components.find((c) => c.type === 'table');
expect(tableComponent).toBeDefined();
expect(tableComponent!.properties.columns).toEqual(['Title', 'Status']);
expect(tableComponent!.properties.rows).toEqual([
['Hello', 'published'],
['Draft', 'draft'],
]);
});
}); });
}); });

View File

@@ -70,7 +70,7 @@ describe('pythonApiContractV1', () => {
it('contains semantic version metadata for compatibility checks', () => { it('contains semantic version metadata for compatibility checks', () => {
expect(BDS_PYTHON_API_CONTRACT_V1).toMatchObject({ expect(BDS_PYTHON_API_CONTRACT_V1).toMatchObject({
version: '1.5.0', version: '1.6.0',
generatedAt: expect.any(String), generatedAt: expect.any(String),
}); });
}); });