AlbertZJ/dsh-genui-stateful

Renderiza UI interativo estruturado inline através do fence dsh-ui. Use para pontos-chave, ênfase, comparações, fluxos, etapas, status, dados, demonstrações e interações sempre que uma apresentação estruturada for mais clara que prosa. Preserve o idioma da conversa para todo texto visível ao usuário.

O que é dsh-genui-stateful?

Esta skill permite que agentes de IA (como Claude Code, Cursor ou Codex) gerem componentes de interface do usuário interativos e estruturados diretamente no meio do texto da resposta, usando um bloco de código fence com a tag `dsh-ui`. O conteúdo do fence é um JSON que descreve layouts, elementos visuais (tabelas, gráficos, linhas do tempo), componentes interativos (botões, menus suspensos, controles deslizantes) e até mesmo visualizadores de código e diagramas. A principal vantagem é substituir explicações puramente textuais por representações visuais mais claras e acionáveis, melhorando a compreensão e a interatividade para o usuário. A skill suporta uma ampla variedade de componentes, desde cards e grades até gráficos ECharts complexos e elementos de formulário, todos renderizados inline. Uma característica fundamental é que as interações do usuário (como cliques em botões) são processadas localmente sempre que possível, minimizando a ida e volta ao modelo, e apenas acionam o modelo novamente quando necessário (por exemplo, para gerar novo conteúdo). A skill também enfatiza a importância de manter o idioma da conversa em todo o texto da interface, garantindo uma experiência consistente para o usuário.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/AlbertZJ/dsh-genui-stateful/tree/main

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

GenUI — Especificação de Saída de UI Generativa

Idioma: Corresponda ao idioma solicitado pelo usuário (caso contrário, o idioma da mensagem dele) tanto na prosa ao redor quanto em todo texto da interface visível ao usuário: títulos, rótulos, conteúdo, opções e explicações. Os exemplos em chinês abaixo ilustram apenas o esquema; não mude a conversa para chinês após carregar esta skill. Mantenha chaves JSON, tipos de componente, IDs e ações inalterados. Uma solicitação em inglês gera prosa e texto de UI em inglês; uma solicitação em chinês gera prosa e texto de UI em chinês. <user-language ...> são meta-placeholders apenas. Substitua cada um por conteúdo real no idioma da conversa; nunca emita esses placeholders literalmente.

Você pode inserir componentes de UI interativos no meio do texto da resposta: escreva um fence dsh-ui (bloco de código com tag de linguagem dsh-ui) contendo uma especificação JSON. O renderizador desenhará este bloco inteiro como componentes reais, com o texto fluindo normalmente antes e depois. O componente é parte da resposta, não uma chamada de ferramenta.

{"title":"<texto no idioma do usuário>","gap":14,"items":[...]}

Regras Obrigatórias para o Fence (Leia esta seção primeiro)

A tag de linguagem na linha de abertura do fence deve ser sempre dsh-ui, sem adicionar pontuação, aspas ou comentários. O renderizador tolera diferenças de maiúsculas/minúsculas, espaços no início/fim e alguma pontuação no final; outras tags de linguagem não serão tratadas como GenUI, e o usuário verá um bloco de código comum.

EscritaResultado
```dsh-ui✅ Renderiza
```dsh-ui>✅ Renderiza com tolerância; ao gerar, ainda remova >
```dsh-ui com espaço no final da linha✅ Renderiza (trim automático)
``` DSH-UI✅ Renderiza (ignora maiúsculas/minúsculas)
```dshui / ```ds-ui❌ Não é uma tag válida

Anteriormente, o mesmo modelo escreveu 182 fences ```dsh-ui> na mesma sessão, causando falhas silenciosas; agora a tolerância para essa pontuação final foi unificada. Ao escrever o fence, use apenas os seis caracteres dsh-ui, sem qualquer pontuação, aspas ou comentários; o fechamento deve ser uma linha separada contendo apenas ```.

Posição do fence: escreva apenas no corpo da resposta. Escrever dentro de blocos de reasoning/pensamento não será renderizado e o usuário não verá.

Fórmulas: $...$ / \(...\) para fórmulas inline, $$...$$ / \[...\] para fórmulas em bloco; suporta matrizes, funções por partes, deduções em várias linhas alinhadas e fórmulas dentro de texto em negrito/destaque. Corpo do texto, listas, pares chave-valor, cabeçalhos/células de tabela, títulos de cartões/etapas/linhas do tempo/abas, perguntas e instruções, rótulos de botões e formulários, métricas, legendas de mídia e títulos externos de gráficos compartilham essa capacidade. As barras invertidas em strings JSON devem ser duplicadas, ex.: "content": "\\(\\frac{a}{b}\\)". Código fonte, valores/placeholders de entrada, opções nativas de dropdown e texto de plotagem interno do mecanismo de gráficos ainda seguem seus próprios formatos nativos, sem análise de rich text. A sintaxe matemática segue o KaTeX; não execute HTML, recursos externos ou scripts; não suporta compilação de documentos LaTeX ou carregamento de pacotes arbitrários.

Vocabulário de Componentes (Apenas esses tipos são permitidos)

Layout: text row col grid card carousel divider spacer arrow hero Exibição: stat badge progress list table keyvalue avatar image image-group audio video timeline file-tree breadcrumb diff json code callout steps icon blocks Gráficos: chart (barras/linha/donut, multi-série) plot (gráfico de função matemática) echart (gráfico ECharts completo) Interação: button input select checkbox radio switch slider textarea tabs accordion copy link submit Avançado: svg mermaid diagram scene3d quiz

Layout

  • text: {"type":"text","size":"display|h1|h2|h3|body|muted|caption","content":"...","align":"left|center|right"?} (tone:"danger|success|warning|info|muted" adiciona cor semântica a toda a linha, sem usar badge; mono:true usa fonte monoespaçada, para hexadecimais/binários/identificadores, sem fundo de código inline) (size:"display" é um tamanho maior que o título, para frases de abertura/destaque, ≤1 por resposta; center:true é a escrita antiga, equivalente a align:"center", sempre use align para novas escritas)
  • row / col: {"type":"row"|"col","items":[...],"wrap":true?,"spacer":true?,"gap":n?}; row.justify:"center" centraliza no eixo principal (como cabeçalho de ícone + título); col.width:n (1-100) define a largura da coluna como porcentagem da largura da linha; se não for especificado, a largura é baseada no conteúdo
  • grid: {"type":"grid","cols":n,"items":[...]}
  • carousel: {"type":"carousel","title":"<título no idioma do usuário>","items":[{"type":"card","title":"...","items":[...]}],"itemWidth":240?,"gap":3?} — Rolagem horizontal de nós nativos; máximo de 24 itens, largura 80–640px e não excedendo 85% da largura do trilho; largura padrão 240px. gap multiplicado por 4px, omitido é 12px, valores negativos tratados como 0. Botões de navegação rolam 85% da largura do trilho a cada clique, exibidos apenas quando o conteúdo transborda; dispositivos de toque deslizam o trilho diretamente. Sem reprodução automática. Quando o trilho está focado, suporta teclas Setas / Home / End; formulários dentro de cartões funcionam de acordo com as regras de envio originais. Navegação e deslizamento não disparam action; actions declaradas por componentes filhos são acionadas de acordo com as regras de interação existentes. Saída em streaming apenas anexa cartões ao final para preservar caminhos de entrada existentes.
  • hero: {"type":"hero","title":"<título no idioma do usuário>","subtitle":"<texto no idioma do usuário>","value":"99.96%","label":"<métrica no idioma do usuário>","delta":"+0.02%","spark":[...],"tone":"accent|success|warning|danger"} — Bloco de capa: sobretítulo + número grande (52px, com contagem de entrada animada) + título + subtítulo + fundo gradiente com tom. Use no máximo um por resposta, coloque-o no início como âncora visual
  • span: Qualquer nó pode receber "span":2 (quantas colunas um nó filho de grid ocupa) — a única primitiva para layout bento: um cartão largo span:2 combinado com um cartão estreito fica muito melhor do que uma coluna de blocos empilhados
  • card: {"type":"card","title":"...","items":[...]}; "tone":"info|success|warning|danger" dá ao cartão uma cor de fundo semântica (para cartões de conclusão/risco); "accent":"#f59e0b" especifica uma cor de destaque (borda + título + fundo muito claro); "flat":true remove sombras e usa um fundo neutro muito claro — painéis em figuras devem usar flat, caso contrário cada camada parecerá um cartão flutuante, sujando o visual após algumas camadas; "eyebrow":"<pequeno subtítulo no idioma do usuário>" adiciona uma linha meta pequena acima do título (use para dividir cartões em capítulos — não coloque em todos os cartões do mesmo capítulo contínuo, ≤3 por resposta)
  • palette: chart / echart suportam "palette":["#ff8800","#3ecf8e"] para substituir a paleta de cores de categorias (padrão segue o tema do host). Escreva apenas quando semanticamente precisar especificar cores (custo=vermelho, ganho=verde), caso contrário, seguir o tema é mais estável
  • divider: {"type":"divider"}; com nome de capítulo {"type":"divider","label":"<nome do capítulo no idioma do usuário>"} (linha + pequeno rótulo centralizado, para mudança de capítulo: ao passar de resumo para dados, abrir um novo capítulo em um álbum de gráficos, use uma vez; não use entre cartões/listas de conteúdo contínuo do mesmo tópico, ≤3 por resposta); spacer: {"type":"spacer"}

Exibição

  • stat: {"type":"stat","label":"...","value":"...","delta":"+12.4%|-3%"} (- no início automaticamente vermelho, + verde); opcional "spark":[3,5,4,8,6] desenha uma linha de micro-tendência (2–60 valores numéricos finitos); "size":"hero" renderiza números gigantes (use no máximo uma vez por resposta, como âncora visual)
  • badge: {"type":"badge","label":"...","tone":"success|warn|danger|accent","icon":"emoji?"}
  • progress: {"type":"progress","label":"...","value":0-100,"valueLabel":"70%"}; "variant":"ring" desenha progresso circular; "target":70 marca uma escala alvo no trilho
  • avatar: {"type":"avatar","name":"...","color":"#hex?"}
  • image: {"type":"image","src":"/mmx-files/result.png","alt":"<descrição no idioma do usuário>"} — Exibe endereços de imagem http(s) acessíveis pelo navegador ou relativos à mesma origem; carregamento lento; não suporta protocolos locais/ativos como file:/data:; "caption":"<legenda no idioma do usuário>" adiciona uma linha de explicação abaixo da imagem (fonte/interpretação/atribuição; alt é a linha de rótulo acima da imagem, ambas com funções diferentes)
  • image-group: {"type":"image-group","title":"<título no idioma do usuário>","items":[{"src":"/mmx-files/a.png","alt":"<descrição>","caption":"<legenda>"},{"src":"/mmx-files/b.png","alt":"<descrição>"}]} — Máximo de 24 imagens que se ajustam por proporção e quebram em novas linhas; clique para abrir em lightbox nativo, teclas Seta / Home / End para navegar, Esc / botão de fechar para retornar e restaurar o foco. Segue as mesmas restrições de http(s)/URL relativa de image; não aceita file/data/javascript. Apenas visualização local, não dispara action; se precisar de ação do modelo, coloque um botão button separado ao lado. Anexar/reordenar preserva o src atual; quando a imagem atual é removida, o visualizador fecha; mesmo src é diferenciado pela ordem de ocorrência. Listas vazias e falhas de carregamento de imagem têm estados explícitos. Exportar HTML não incorpora bytes de imagem, as imagens ainda dependem do endereço original. Uso completo e verificação.
  • audio: {"type":"audio","src":"/mmx-files/result.mp3","alt":"<descrição no idioma do usuário>","loop":true?} — Controles nativos; reprodução iniciada pelo usuário, não automática; apenas http(s) ou URL relativa à mesma origem
  • video: {"type":"video","src":"/mmx-files/result.mp4","alt":"<descrição no idioma do usuário>","poster":"/mmx-files/poster.jpg"?,"loop":true?,"muted":true?,"aspectRatio":"16:9|4:3|1:1|9:16"?} — Controles nativos de reprodução/volume/tela cheia; não reproduz automaticamente
  • list: {"type":"list","items":["..."] ou [{"title":"...","desc":"..."}] ou nós aninhados (ex.: {"type":"badge","label":"TS"})} — Nós inline podem ser incorporados (contam para o orçamento de nós); em registros, label/name são normalizados para title, description/content/text/body/detail são normalizados para desc; se apenas o corpo do texto estiver presente, ele é usado como título; campos desconhecidos geram um aviso
  • table: {"type":"table","columns":["..."],"rows":[["...","..."]],"types":["text|num|delta|bar|badge"]?,"details":[[...]]?,"total":true?} — Clique no cabeçalho para ordenação local (crescente/decrescente/restaurar, zero ida e volta); reconhecimento numérico: separador de milhares (1.234), k/m/b, 万/亿, %, símbolos de moeda são comparados como valores numéricos reais, colunas puramente numéricas são automaticamente alinhadas à direita; células com sinal são coloridas automaticamente (+12.4% verde, -3 vermelho, nenhum campo extra necessário); types pode especificar por coluna: bar (barra de progresso inline 0-100), ring (pequeno anel 0-100), spark (escreva "3,5,4,8" na célula para desenhar uma linha de micro-tendência), badge (rótulo cápsula), delta (força cor de variação), num (força alinhamento à direita), index (número da linha), group (primeira coluna como título de grupo: se a linha tiver conteúdo apenas na primeira célula, renderiza como subtítulo de várias colunas); "total":true adiciona uma linha de total (colunas numéricas são somadas automaticamente); "export":true: dois pequenos botões "Copiar Markdown / Copiar CSV" aparecem acima da tabela (apenas área de transferência local, sem requisições); "filter":"id_do_input": vincula a tabela a um input/select, o leitor digita para filtrar instantaneamente (filterColumn pode limitar a coluna) — com muitos dados, deve-se configurar um por padrão; "sortField":"id_do_dropdown" usa o valor do dropdown (nome da coluna) para ordenar; details na mesma ordem de rows, o i-ésimo item é o conteúdo expandido daquela linha (pode conter qualquer componente, null = linha não expansível) — um chevron aparece na primeira coluna, clicar expande os detalhes abaixo da linha, ideal para "tabela principal + detalhes"
  • keyvalue: {"type":"keyvalue","pairs":[{"key":"...","value":"..."}]}
  • timeline: {"type":"timeline","items":[{"title":"...","desc":"...","time":"..."}]}
  • file-tree: {"type":"file-tree","items":[{"name":"...","type":"file|dir","children":[...]?}]} — Linhas de diretório podem ser clicadas para recolher/expandir (local, zero ida e volta)
  • breadcrumb: {"type":"breadcrumb","items":["<texto no idioma do usuário>","<texto no idioma do usuário>","<texto no idioma do usuário>"]}
  • diff: {"type":"diff","diffs":[{"path":"...","oldText":"..."|null,"newText":"..."}]}
  • json: {"type":"json","value":...} (visualizador de árvore JSON)
  • code: {"type":"code","lang":"ts","code":"..."}
  • callout: {"type":"callout","tone":"info|success|warning|error","title":"...","content":"..."} — Para ênfase única, lembrete ou resumo de risco; content é uma string de texto, pode usar "\n" do JSON para quebrar linhas. Para múltiplos itens independentes, use list com {title,desc} para manter a hierarquia item por item
  • steps: {"type":"steps","current":n,"steps":[{"title":"...","desc":"..."}]}
  • arrow: {"type":"arrow","dir":"down|right","length":16-96,"label":"..."} — Conector de linha fina entre dois blocos em um diagrama (largura de linha 1.4px + seta em V, comprimento fixo). Use sempre para fluxogramas de cima para baixo, não use o caractere ↓: as setas de caractere seguem a linha de base do texto, não abrem espaçamento
  • icon: {"type":"icon","name":"Nodes","tone":"info|success|warning|danger|muted|default","size":16|20|24|32,"tile":true,"label":"<nome acessível no idioma do usuário>"} — Conjunto de ícones embutidos (749 podem ser renderizados fielmente; 64 carregam instantaneamente inline, os demais são carregados no primeiro uso, lista completa em ICON-NAMES.md). tile:true desenha como fundo quadrado arredondado claro, usado para cartões de etapa/marcação semântica; sem tile, é um ícone inline que segue a cor do texto. label é opcional (puramente decorativo); quando aparecem em pares, apenas um recebe label
  • blocks: {"type":"blocks","title":"...","rows":[{"label":"Inicial","cells":["0","1","2"],"color":"#64748b"}],"columns":n?,"caption":"..."} — Diagrama de blocos coloridos: células agrupadas em linhas. Três formas, mesma estrutura: ① Diagrama de escada (cada linha tem número diferente de células, cada linha divide a largura igualmente, ex.: 8→4→2→1) ② Blocos iguais (4 blocos por linha) ③ Diagrama de pistas (número fixo de colunas em columns, escreva "—" ou deixe vazio em cells). Se color for omitido, usa fundo claro da cor de destaque; a cor do texto é invertida automaticamente com base no brilho (texto branco em fundo escuro / texto preto em fundo claro). Use para estruturas que envolvam "dividir em partes, progredir rodada a rodada, comparar grupos", não escreva coordenadas SVG manualmente. caption é obrigatório: uma frase explicando o que cada célula representa
    • Nível de linha "variant":"outline": borda fina sem preenchimento, para matrizes (cada célula é uma caixa); "variant":"plain": sem borda, para strings de bits (1 0 1 0 0 0 0 0 lido como número, não tabela) — apenas células com tone/color terão cor de fundo
    • Células podem ser escritas como objetos: {"text":"1","tone":"danger","sub":"SF"} — tone (accent/success/warning/danger/muted) dá cor semântica a células individuais, sub coloca uma pequena linha abaixo da célula (nome do bit, número, papel). color pode especificar explicitamente um hexadecimal, tem precedência sobre tone
    • Ex.: {"columns":8,"rows":[{"variant":"outline","cells":[{"text":"1","tone":"danger","sub":"SF"},"0","1","1","0","0","1","0","0"]}],"caption":"O bit mais significativo é 1, então SF=1."}

Nomes de ícones embutidos: Os seguintes 64 estão no pacote e renderizam instantaneamente, escolha preferencialmente entre eles:

Check CheckCircle Info InfoCircle Warning Error X Plus Minus Circle ArrowDown ArrowUp ArrowRight ArrowLeft ArrowCurvedRight Function Functions Operator Atom Nodes Connect Share Branch Grid GridAlt Stack Cube Storage Chart Trending Pulse Speed Timer Clock TableFilled Code SquareCode Terminal File FileCode Folder Link Paperclip Lightbulb Book BookOpen GraduationCap Flask Lab Brain Robot Sparkles ShieldCheck Lock Globe WebsiteNetwork Search MagnifyingGlassSearch PluginPuzzle Settings Bolt Compass

Os outros 685 são carregados sob demanda (cerca de 1 MB baixados no primeiro uso, disponíveis para a página inteira a partir de então). A lista completa está no arquivo ICON-NAMES.md no mesmo diretório desta SKILL — leia-o ou use grep para adivinhar nomes, não enumere no diálogo.

Gráficos

  • chart: {"type":"chart","kind":"bars|line|donut","data":[{"label":"...","value":n,"color":"#hex?"}],"series":[{"label":"...","data":[...]}]?,"horizontal":true?} — bars padrão; line tendência; donut proporção; series: bars são barras agrupadas, line são linhas multi-série; cada series[].data usa um array de objetos como [{"label":"Q1","value":30},{"label":"Q2","value":45}], não um array de números simples como [30,45]; horizontal:true desenha barras horizontais (ideal para rankings/rótulos longos); stacked:true empilha as series (composição/proporção ao longo do tempo); quando o segmento empilhado é alto o suficiente, o valor é impresso diretamente dentro do segmento; passar o mouse sobre qualquer barra/segmento/ponto/setor exibe um tooltip instantâneo (empilhado mostra o valor do segmento + total). Renderização v3: largura adaptativa, eixo Y com escala 1/2/5, valores negativos de série única são desenhados realisticamente abaixo da linha zero, line com gradiente de área e rótulos X reduzidos, legenda de donut mostra valor e porcentagem. Use chart para comparações rápidas com ≤8 pontos; use echart para multi-série, necessidade de zoom/interação ou grandes volumes de dados
  • plot: {"type":"plot","series":[{"expr":"a*sin(b*x)","label":"...","color":"#hex?","params":[{"name":"a","value":1,"min":0,"max":5,"animateTo":3,"durationMs":4000,"loop":true},{"name":"b","value":1,"min":0.5,"max":5}]}],"xMin":-6.28,"xMax":6.28,"title":"..."} — Gráfico de função SVG; series pode ter "kind":"line|area|scatter" (padrão line; area preenche até a linha de base; scatter dispersão); params renderiza como controles deslizantes em tempo real (arrastar redesenha instantaneamente, eixo Y fixo = apenas a curva muda, o eixo não); parâmetro animateTo exibe um botão de reprodução (demonstração automática com animação); o SVG pode ser arrastado para mover, zoom com roda do mouse; expressões suportam sin/cos/tan/asin/acos/atan/sqrt/cbrt/exp/log/ln/abs/floor/ceil/round/min/max/pow, constantes pi/e/tau, variável x (outras letras = parâmetros)
  • echart: {"type":"echart","title":"...","height":300,"preset":"bar|line|area|pie|scatter","data":[{"label":"...","value":n}],"series":[...]?} — Gráfico ECharts completo, efeito visual muito superior a chart (gradiente, tooltip, animação, interação de legenda); modo preset: use o mesmo formato data/series de chart, constrói automaticamente a configuração ECharts tematizada (cores seguem o tema do host); Lista de presets (apenas escreva preset + data/series/links, o tema segue automaticamente): bar · line · area · pie · scatter · radar (cada série um polígono, métricas usam os rótulos da primeira série) · gauge (cada datum um medidor, ideal para KPI único) · funnel (funil/conversão) · treemap (volume/proporção hierárquica) · sankey (fluxo, use links:[{from,to,value}]) · graph (grafo de relações, links direciona, tamanho do nó segue número de conexões) · heatmap (series como linhas, rótulos da primeira série como colunas) · bigline (sequência longa + zoom embutido) · wordCloud (nuvem de palavras, data:[{label,value}] onde value é o peso; cores seguem palette ou paleta do tema) Modo option completa: passe "option":{...} para escrever a configuração nativa do ECharts diretamente (suporta gráficos embutidos do ECharts e a extensão wordCloud registrada; outras extensões de terceiros não são garantidas). Funções na option são filtradas (apenas dados são aceitos). Princípio de escolha: chart é leve (sem download extra), adequado para comparações rápidas com ≤8 pontos; echart é mais rico visualmente (gradiente, tooltip, interação de legenda, dataZoom), mas baixa cerca de 1 MB de engine sob demanda; priorize para multi-série/telas grandes/cenários interativos

Interação

Prioridade local (v2.6): Mudanças de estado que a própria UI pode fazer — corrigir, avaliar, redefinir, expandir, selecionar — são feitas instantaneamente localmente, zero ida e volta ao modelo. action é usada apenas para coisas que exigem participação do modelo (gerar novo conteúdo, executar ferramentas, sugerir próximo passo). Componentes interativos devem ter action: botões sem action são renderizados como desabilitados, o usuário não pode clicar; botões com action têm feedback local de "acionado" após o clique.

  • button: {"type":"button","label":"...","tone":"primary|danger|success|ghost","full":true?,"small":true?,"icon":"emoji?","action":"refresh"?}
  • Proibição absoluta: Não solicite nem gere senhas, chaves de API, tokens de acesso, códigos de recuperação ou quaisquer entradas secretas; recuse tais solicitações diretamente e explique
  • input: {"type":"input","label":"...","placeholder":"...","inputType":"text|email|color|number","value":"...","action":"name"?,"id":"id-do-campo"?} — color usa o seletor de cores nativo do navegador, valor no formato #RRGGBB; number pode ser usado como entrada numérica Computed; action é acionado ao perder o foco e ao pressionar Enter (Enter envia submit:true); blur só envia se o valor mudou (focar e sair sem ação não gera ida e volta vazia); o payload carrega id para ajudar o modelo a localizar o campo; valores com id são preservados após atualização e coletados pelo submit em fields
  • select: {"type":"select","label":"...","options":["...","..."],"selected":índice?,"action":"pick"?,"id":"id-do-campo"?} — selected pré-seleciona uma opção (padrão mostra placeholder "Selecione...", sem pré-selecionar silenciosamente o primeiro item); seleções com id são preservadas entre atualizações e entram em fields do submit
  • checkbox: {"type":"checkbox","label":"<opção no idioma do usuário>","checked":true?,"action":"toggle"?,"group":"id-do-grupo"?} — Comportamento padrão: action a cada alternância; com group, entra em modo de agregação multisseleção: checkboxes do mesmo grupo podem ser marcadas/desmarcadas repetidamente, as mudanças são registradas apenas localmente, sem enviar action a cada vez; um submit irmão envia de uma vez os labels selecionados como um array de strings em answers (ex.: {"styles":["<opção no idioma do usuário>","<opção no idioma do usuário>"]})
  • slider: {"type":"slider","label":"...","min":0,"max":100,"step":1,"value":n?,"action":"name"?,"id":"id-do-campo"?} — Controle deslizante de formulário numérico: exibe o valor em tempo real; com id, preserva o valor entre atualizações e entra em fields do submit (arrastar é debounced para consolidar em uma única action)
  • radio: {"type":"radio","label":"<rótulo no idioma do usuário>","options":["<opção no idioma do usuário>","<opção no idioma do usuário>"],"selected":n?,"action":"pick"?} — Seleção única; adicione "group":"id-do-grupo" para modo de agregação: a seleção é registrada apenas localmente, sem ida e volta; pode definir "answer":índice_ou_rótulo_correto e "explanation":"<explicação no idioma do usuário>", e quando é apenas um radio sem outros estados de formulário a enviar, a correção é feita localmente
  • link: {"type":"link","label":"...","href":"https://..."?} — Apenas protocolos http(s)/mailto são aceitos; sem href, renderiza como texto simples (não finge ser clicável)
  • submit: {"type":"submit","label":"<ação no idioma do usuário>","action":"grade","groups":["q1","styles"],"resetAction":"redo"?} — Botão de agregação: groups referencia chaves de membros de submission no bloco atual, provenientes de radio.group, checkbox.group, input.id, textarea.id, select.id, slider.id; radio deve estar selecionado, grupo de checkbox deve ter pelo menos um item selecionado, valor de campo comum deve ser não vazio após trim. groups controla os membros necessários para envio e o progresso de conclusão; quando não definido, pelo menos um membro concluído é necessário para enviar. Quando o escopo do radio contém answer e não há payload fora do escopo desta vez, a correção é local; em outros cenários, envia [genui-action] uma vez, e o payload continua coletando o estado do formulário preenchido no bloco atual.

Avançado

  • svg: {"type":"svg","svg":"<svg>...</svg>"} — Renderiza SVG inline arbitrário. Apenas elementos estáticos e animações CSS; sem interação com script. Acessível via alt? Use com moderação e apenas quando formas/componentes existentes não conseguirem expressar o visual necessário.
  • mermaid: {"type":"mermaid","code":"..."} — Diagramas e gráficos Mermaid (sequência, fluxograma, classe, estado, Gantt, pizza, requisitos, git). Use com moderação: o Mermaid é uma dependência grande (~2 MB), o carregamento atrasará a renderização e a experiência do usuário; prefira usar componentes integrados (diff, table, timeline, echart, chart, blocks, arrow, etc.) tanto quanto possível. Restrições de segurança: sem interação, sem chamadas de rede, sem acesso DOM.
  • diagram: {"type":"diagram","nodes":[{"id":"...","label":"...","type":"process|decision|start|end|input|output"}],"edges":[{"from":"...","to":"...","label":"..."?}]} — Diagrama de fluxo simples: nós e arestas renderizados com SVG. Alternativa ao Mermaid, mais leve, mas com menos funcionalidades.
  • scene3d: {"type":"scene3d","objects":[...],"camera":[...],"controls":"orbit|fly|none","lighting":"ambient|directional","backgroundColor":"#hex?","width":"100%","height":400} — Cena 3D interativa (baseada em Three.js). Objetos podem ser formas primitivas (caixa, esfera, cilindro, cone, toróide, plano, anel, círculo) ou carregar malhas externas (.glb/.gltf). Suporta rotação, zoom com mouse/teclado, rotação automática, animação e luzes. Apenas para representação de dados tridimensionais, modelos moleculares, estruturas geométricas, visualização de arquitetura ou outros cenários que exigem compreensão 3D; não use para efeitos puramente decorativos. [Documentação completa em docs/SCENE3D.md]
  • quiz: {"type":"quiz","question":"<pergunta no idioma do usuário>","options":["<opção no idioma do usuário>","<opção no idioma do usuário>"],"answer":índice,"explanation":"<explicação no idioma do usuário>"?,"multiple":false?} — Renderiza uma pergunta de quiz interativa. Resposta verificada localmente; feedback (correto/incorreto) e explicação são exibidos imediatamente. multiple:true permite múltiplas respostas corretas (vários índices em answer).

Orçamento de Nós

Cada componente dentro de um ```dsh-ui fence consome uma certa quantidade de "nós" do orçamento de renderização. O limite é de 300 nós por fence (contagem total de componentes, incluindo aninhados). Aplicações únicas que ultrapassam o limite não serão renderizadas; considere dividir em vários fences.

Contagem de nós:

  • Componentes de layout (row, col, grid, card): contam como 1 nó cada + filhos
  • text: conta como 1 nó
  • stat, badge, progress: 1 nó cada
  • table: 1 nó base + 1 nó por linha
  • list: 1 nó base + 1 nó por item
  • chart, echart, plot: 5 nós cada (devido à complexidade de renderização)
  • image: 2 nós (incluindo carregamento)
  • button, input, select, checkbox, radio, slider, submit: 2 nós cada
  • Divider, spacer: 0.5 nós (arredondado para 1)
  • callout, steps: 2 nós cada
  • code, json: 3 nós cada
  • mermaid, svg, scene3d: 5 nós cada
  • Outros: 1 nó cada

Regras de Idioma e Metadados

  1. Idioma da Interface: Todo texto visível ao usuário — títulos, rótulos, conteúdo, opções, explicações, placeholders, legendas — deve estar no idioma da conversa atual. Isso se aplica a todos os componentes. Se a conversa estiver em português, todo texto da UI deve estar em português.

  2. Marcadores <user-language ...>: Estes são meta-placeholders que você deve substituir por texto real no idioma da conversa. Nunca os emita literalmente.

  3. Consistência: Mantenha o idioma consistente em toda a resposta. Se o usuário mudar de idioma no meio da conversa, acompanhe a mudança.

  4. Campos não traduzíveis: Os seguintes campos JSON nunca devem ser traduzidos ou alterados:

    • type (tipos de componente)
    • name (nomes de ícones)
    • id (identificadores de campo/grupo)
    • href, src (URLs)
    • lang (tags de idioma para blocos de código)
    • action (nomes de ação)
    • preset (nomes de predefinição de echart)
    • kind (tipo de gráfico)
    • variant
    • from, to (links Sankey/grafo)

Exemplo Completo

{
  "type": "hero",
  "title": "Análise de Vendas Q4",
  "subtitle": "Desempenho trimestral e projeções",
  "value": "R$ 12,4M",
  "label": "Receita Total",
  "delta": "+18.5%",
  "spark": [10, 12, 15, 18, 22, 28, 30, 35, 38, 40, 42, 45],
  "tone": "success"
}
{
  "type": "grid",
  "cols": 2,
  "items": [
    {
      "type": "card",
      "title": "Produto Principal",
      "items": [
        {"type": "stat", "label": "Unidades Vendidas", "value": "24.500", "delta": "+12%"},
        {"type": "stat", "label": "Ticket Médio", "value": "R$ 506", "delta": "+5%"}
      ]
    },
    {
      "type": "card",
      "title": "Novo Produto",
      "items": [
        {"type": "stat", "label": "Unidades Vendidas", "value": "8.200", "delta": "+45%"},
        {"type": "stat", "label": "Ticket Médio", "value": "R$ 320", "delta": "-2%"}
      ]
    }
  ]
}

Notas de Implementação

  • Todos os ```dsh-ui fences são renderizados inline no fluxo da conversa, não como chamadas de ferramenta separadas.
  • O renderizador é responsável por converter a especificação JSON em componentes de UI reais.
  • O sistema de orçamento de nós garante que a UI permaneça responsiva.
  • Interações locais (seleção de guias, expansão de árvore, classificação de tabela) nunca acionam uma nova execução do modelo.
  • Ações (cliques em botões, envios de formulário) que exigem processamento do modelo acionam uma ação [genui-action] com o payload apropriado.
  • Para cenários de streaming, componentes como carousel e list podem ser anexados progressivamente, desde que a estrutura JSON geral permaneça válida.
  • Sempre preferia componentes integrados em vez de escrever HTML/SVG/Mermaid personalizado, a menos que haja um requisito de visualização muito específico que não possa ser atendido.

Habilidades Relacionadas