@meistrari/chat-ui-lang 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -13
- package/dist/actions.d.ts +9 -0
- package/dist/answers.d.ts +16 -0
- package/dist/components/actions.d.ts +47 -0
- package/dist/components/chart.d.ts +23 -0
- package/dist/components/chat.d.ts +55 -0
- package/dist/components/data.d.ts +31 -0
- package/dist/components/form.d.ts +42 -0
- package/dist/components/layout.d.ts +1179 -0
- package/dist/components/text.d.ts +14 -0
- package/dist/define.d.ts +8 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +935 -0
- package/dist/library.d.ts +6 -0
- package/dist/nuxt/module.d.mts +9 -0
- package/dist/nuxt/module.json +9 -0
- package/dist/nuxt/module.mjs +31 -0
- package/dist/nuxt/runtime/actions.d.ts +15 -0
- package/dist/nuxt/runtime/actions.js +40 -0
- package/dist/nuxt/runtime/components/openui-widget.d.vue.ts +21 -0
- package/dist/nuxt/runtime/components/openui-widget.vue +72 -0
- package/dist/nuxt/runtime/components/openui-widget.vue.d.ts +21 -0
- package/dist/nuxt/runtime/components/plain-prose.d.vue.ts +7 -0
- package/dist/nuxt/runtime/components/plain-prose.vue +12 -0
- package/dist/nuxt/runtime/components/plain-prose.vue.d.ts +7 -0
- package/dist/nuxt/runtime/components/widgets/bar-chart.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/bar-chart.vue +39 -0
- package/dist/nuxt/runtime/components/widgets/bar-chart.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/bar.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/bar.vue +12 -0
- package/dist/nuxt/runtime/components/widgets/bar.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/button.d.vue.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/button.vue +30 -0
- package/dist/nuxt/runtime/components/widgets/button.vue.d.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/buttons.d.vue.ts +8 -0
- package/dist/nuxt/runtime/components/widgets/buttons.vue +14 -0
- package/dist/nuxt/runtime/components/widgets/buttons.vue.d.ts +8 -0
- package/dist/nuxt/runtime/components/widgets/callout.d.vue.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/callout.vue +27 -0
- package/dist/nuxt/runtime/components/widgets/callout.vue.d.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/card.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/card.vue +19 -0
- package/dist/nuxt/runtime/components/widgets/card.vue.d.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/checkbox.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/checkbox.vue +26 -0
- package/dist/nuxt/runtime/components/widgets/checkbox.vue.d.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/citation.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/citation.vue +9 -0
- package/dist/nuxt/runtime/components/widgets/citation.vue.d.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/col.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/col.vue +16 -0
- package/dist/nuxt/runtime/components/widgets/col.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/file-card.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/file-card.vue +9 -0
- package/dist/nuxt/runtime/components/widgets/file-card.vue.d.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/form.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/form.vue +60 -0
- package/dist/nuxt/runtime/components/widgets/form.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/image.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/image.vue +34 -0
- package/dist/nuxt/runtime/components/widgets/image.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/input.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/input.vue +23 -0
- package/dist/nuxt/runtime/components/widgets/input.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/markdown.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/markdown.vue +14 -0
- package/dist/nuxt/runtime/components/widgets/markdown.vue.d.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/metric.d.vue.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/metric.vue +20 -0
- package/dist/nuxt/runtime/components/widgets/metric.vue.d.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/option.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/option.vue +12 -0
- package/dist/nuxt/runtime/components/widgets/option.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/question.d.vue.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/question.vue +15 -0
- package/dist/nuxt/runtime/components/widgets/question.vue.d.ts +11 -0
- package/dist/nuxt/runtime/components/widgets/questions.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/questions.vue +91 -0
- package/dist/nuxt/runtime/components/widgets/questions.vue.d.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/select.d.vue.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/select.vue +27 -0
- package/dist/nuxt/runtime/components/widgets/select.vue.d.ts +10 -0
- package/dist/nuxt/runtime/components/widgets/stack.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/stack.vue +18 -0
- package/dist/nuxt/runtime/components/widgets/stack.vue.d.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/table.d.vue.ts +9 -0
- package/dist/nuxt/runtime/components/widgets/table.vue +41 -0
- package/dist/nuxt/runtime/components/widgets/table.vue.d.ts +9 -0
- package/dist/nuxt/runtime/context.d.ts +30 -0
- package/dist/nuxt/runtime/context.js +4 -0
- package/dist/nuxt/runtime/form-fields.d.ts +10 -0
- package/dist/nuxt/runtime/form-fields.js +30 -0
- package/dist/nuxt/runtime/program.d.ts +15 -0
- package/dist/nuxt/runtime/program.js +74 -0
- package/dist/nuxt/runtime/registry.d.ts +4 -0
- package/dist/nuxt/runtime/registry.js +44 -0
- package/dist/nuxt/runtime/render.d.ts +21 -0
- package/dist/nuxt/runtime/render.js +48 -0
- package/dist/nuxt/runtime/use-openui-context.d.ts +2 -0
- package/dist/nuxt/runtime/use-openui-context.js +8 -0
- package/dist/nuxt/types.d.mts +9 -0
- package/dist/profiles.d.ts +10 -0
- package/dist/prompt.d.ts +5 -0
- package/dist/safe-url.d.ts +2 -0
- package/dist/segments.d.ts +16 -0
- package/dist/slack.d.ts +28 -0
- package/dist/version.d.ts +2 -0
- package/dist/widget-context.d.ts +13 -0
- package/package.json +72 -31
- package/src/actions.ts +0 -12
- package/src/components/actions.ts +0 -21
- package/src/components/chart.ts +0 -22
- package/src/components/chat.ts +0 -60
- package/src/components/data.ts +0 -32
- package/src/components/form.ts +0 -41
- package/src/components/layout.ts +0 -30
- package/src/components/text.ts +0 -20
- package/src/define.ts +0 -9
- package/src/index.ts +0 -14
- package/src/library.ts +0 -64
- package/src/prompt.ts +0 -54
- package/src/version.ts +0 -2
package/dist/index.js
ADDED
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
// src/actions.ts
|
|
2
|
+
import { defineAction } from "@openuidev/lang-core";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
var CopyText = defineAction({
|
|
5
|
+
name: "CopyText",
|
|
6
|
+
description: "Copia um texto para a área de transferência do usuário.",
|
|
7
|
+
params: z.object({
|
|
8
|
+
text: z.string()
|
|
9
|
+
})
|
|
10
|
+
});
|
|
11
|
+
var actions = { CopyText };
|
|
12
|
+
// src/answers.ts
|
|
13
|
+
function formatQuestionAnswers(requestId, questions, answers) {
|
|
14
|
+
if (!requestId.trim() || questions.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const lines = [];
|
|
17
|
+
for (const question of questions) {
|
|
18
|
+
const answer = Object.hasOwn(answers, question.id) ? answers[question.id] : undefined;
|
|
19
|
+
if (answer?.skipped) {
|
|
20
|
+
lines.push(`${question.title}: Pulada pelo usuário`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const selected = question.options.filter((option) => answer?.selected.includes(option.id));
|
|
24
|
+
if (!selected.length || question.type === "single" && selected.length > 1)
|
|
25
|
+
return null;
|
|
26
|
+
lines.push(`${question.title}: ${selected.map((option) => option.label).join("; ")}`);
|
|
27
|
+
}
|
|
28
|
+
return `Respostas (${requestId.trim()}):
|
|
29
|
+
${lines.join(`
|
|
30
|
+
`)}`;
|
|
31
|
+
}
|
|
32
|
+
// src/components/actions.ts
|
|
33
|
+
import { action } from "@openuidev/lang-core";
|
|
34
|
+
import { z as z2 } from "zod";
|
|
35
|
+
|
|
36
|
+
// src/define.ts
|
|
37
|
+
import { defineComponent as defineLangComponent } from "@openuidev/lang-core";
|
|
38
|
+
function defineUiComponent(config) {
|
|
39
|
+
return defineLangComponent({ ...config, component: null });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/components/actions.ts
|
|
43
|
+
var Button = defineUiComponent({
|
|
44
|
+
name: "Button",
|
|
45
|
+
description: "Botão de ação. Com @ToAssistant envia uma mensagem como se o usuário tivesse digitado.",
|
|
46
|
+
props: z2.object({
|
|
47
|
+
label: z2.string(),
|
|
48
|
+
action: action().optional(),
|
|
49
|
+
variant: z2.enum(["primary", "secondary"]).optional().describe("Padrão secondary; no máximo um primary por grupo")
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
var Buttons = defineUiComponent({
|
|
53
|
+
name: "Buttons",
|
|
54
|
+
description: "Grupo horizontal de botões.",
|
|
55
|
+
props: z2.object({
|
|
56
|
+
buttons: z2.array(Button.ref).min(1).max(4)
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
// src/components/chart.ts
|
|
60
|
+
import { z as z3 } from "zod";
|
|
61
|
+
var Bar = defineUiComponent({
|
|
62
|
+
name: "Bar",
|
|
63
|
+
description: "Uma barra do gráfico.",
|
|
64
|
+
props: z3.object({
|
|
65
|
+
label: z3.string(),
|
|
66
|
+
value: z3.number().describe("Valor numérico usado para o tamanho da barra"),
|
|
67
|
+
display: z3.string().optional().describe('Valor formatado para exibir, ex: "R$ 1,2 mi"')
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
var BarChart = defineUiComponent({
|
|
71
|
+
name: "BarChart",
|
|
72
|
+
description: "Gráfico de barras horizontais para comparar até 8 valores da mesma unidade.",
|
|
73
|
+
props: z3.object({
|
|
74
|
+
title: z3.string(),
|
|
75
|
+
bars: z3.array(Bar.ref).min(2).max(8),
|
|
76
|
+
tone: z3.enum(["neutral", "positive", "negative"]).optional()
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
// src/components/chat.ts
|
|
80
|
+
import { z as z4 } from "zod";
|
|
81
|
+
var Option = defineUiComponent({
|
|
82
|
+
name: "Option",
|
|
83
|
+
description: "Uma alternativa de resposta.",
|
|
84
|
+
props: z4.object({
|
|
85
|
+
id: z4.string().describe("Identificador curto e único dentro da pergunta"),
|
|
86
|
+
label: z4.string(),
|
|
87
|
+
description: z4.string().optional().describe("Uma linha explicando a alternativa")
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
var Question = defineUiComponent({
|
|
91
|
+
name: "Question",
|
|
92
|
+
description: "Pergunta de múltipla escolha para o usuário. Entre 2 e 4 alternativas.",
|
|
93
|
+
props: z4.object({
|
|
94
|
+
id: z4.string(),
|
|
95
|
+
title: z4.string(),
|
|
96
|
+
options: z4.array(Option.ref).min(2).max(4),
|
|
97
|
+
multiple: z4.boolean().optional().describe("true permite marcar mais de uma alternativa")
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
var Questions = defineUiComponent({
|
|
101
|
+
name: "Questions",
|
|
102
|
+
description: "Bloco de perguntas de esclarecimento. As respostas voltam como uma mensagem do usuário. Use no máximo 3 perguntas e só quando a resposta depender delas.",
|
|
103
|
+
props: z4.object({
|
|
104
|
+
requestId: z4.string().describe('Identificador curto desta rodada de perguntas, ex: "q-prazos"'),
|
|
105
|
+
questions: z4.array(Question.ref).min(1).max(3)
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
var FileCard = defineUiComponent({
|
|
109
|
+
name: "FileCard",
|
|
110
|
+
description: "Cartão de um documento do vault, com preview e download.",
|
|
111
|
+
props: z4.object({
|
|
112
|
+
vaultRef: z4.string().describe("Referência vault:// do documento, exatamente como recebida"),
|
|
113
|
+
name: z4.string().describe("Nome exibido do arquivo")
|
|
114
|
+
})
|
|
115
|
+
});
|
|
116
|
+
var Citation = defineUiComponent({
|
|
117
|
+
name: "Citation",
|
|
118
|
+
description: "Chip de citação para um trecho de documento. Prefira citações inline no markdown; use este componente dentro de Card ou Stack quando a citação for o item em si.",
|
|
119
|
+
props: z4.object({
|
|
120
|
+
ref: z4.string().describe("Referência cite:// exatamente como recebida"),
|
|
121
|
+
label: z4.string().optional().describe("Texto curto exibido no chip")
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
var Image = defineUiComponent({
|
|
125
|
+
name: "Image",
|
|
126
|
+
description: "Imagem com legenda. Aceita referência vault:// de uma imagem gerada ou recebida, ou uma URL https pública.",
|
|
127
|
+
props: z4.object({
|
|
128
|
+
src: z4.string().describe("vault://... ou https://..., exatamente como recebida"),
|
|
129
|
+
alt: z4.string().describe("Descrição curta da imagem"),
|
|
130
|
+
caption: z4.string().optional()
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
// src/components/data.ts
|
|
134
|
+
import { z as z5 } from "zod";
|
|
135
|
+
var Metric = defineUiComponent({
|
|
136
|
+
name: "Metric",
|
|
137
|
+
description: "Um número de destaque com rótulo. Use para valores isolados, nunca para listas.",
|
|
138
|
+
props: z5.object({
|
|
139
|
+
label: z5.string().describe("O que o número representa"),
|
|
140
|
+
value: z5.string().describe('Valor já formatado, ex: "R$ 12.400,00"'),
|
|
141
|
+
delta: z5.string().optional().describe('Variação, ex: "+12%"'),
|
|
142
|
+
tone: z5.enum(["neutral", "positive", "negative"]).optional()
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
var Col = defineUiComponent({
|
|
146
|
+
name: "Col",
|
|
147
|
+
description: "Uma coluna de tabela: rótulo e a lista de valores, um por linha.",
|
|
148
|
+
props: z5.object({
|
|
149
|
+
label: z5.string(),
|
|
150
|
+
values: z5.array(z5.union([z5.string(), z5.number()])).describe("Um valor por linha, na mesma ordem em todas as colunas"),
|
|
151
|
+
align: z5.enum(["left", "right"]).optional().describe("right para números")
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
var Table = defineUiComponent({
|
|
155
|
+
name: "Table",
|
|
156
|
+
description: "Tabela orientada a colunas. Use para 3 ou mais linhas comparáveis.",
|
|
157
|
+
props: z5.object({
|
|
158
|
+
columns: z5.array(Col.ref).min(1),
|
|
159
|
+
caption: z5.string().optional()
|
|
160
|
+
})
|
|
161
|
+
});
|
|
162
|
+
// src/components/form.ts
|
|
163
|
+
import { z as z6 } from "zod";
|
|
164
|
+
var Input = defineUiComponent({
|
|
165
|
+
name: "Input",
|
|
166
|
+
description: "Campo de texto livre de uma linha.",
|
|
167
|
+
props: z6.object({
|
|
168
|
+
name: z6.string().describe("Identificador do campo dentro do formulário"),
|
|
169
|
+
label: z6.string(),
|
|
170
|
+
placeholder: z6.string().optional()
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
var Select = defineUiComponent({
|
|
174
|
+
name: "Select",
|
|
175
|
+
description: "Escolha única entre alternativas curtas.",
|
|
176
|
+
props: z6.object({
|
|
177
|
+
name: z6.string(),
|
|
178
|
+
label: z6.string(),
|
|
179
|
+
options: z6.array(z6.string()).min(2).max(8)
|
|
180
|
+
})
|
|
181
|
+
});
|
|
182
|
+
var Checkbox = defineUiComponent({
|
|
183
|
+
name: "Checkbox",
|
|
184
|
+
description: "Caixa de marcação sim/não.",
|
|
185
|
+
props: z6.object({
|
|
186
|
+
name: z6.string(),
|
|
187
|
+
label: z6.string()
|
|
188
|
+
})
|
|
189
|
+
});
|
|
190
|
+
var Form = defineUiComponent({
|
|
191
|
+
name: "Form",
|
|
192
|
+
description: "Formulário curto para coletar dados que faltam. Ao enviar, os valores voltam como uma mensagem do usuário.",
|
|
193
|
+
props: z6.object({
|
|
194
|
+
name: z6.string().describe('Identificador do formulário, ex: "dados-prazo"'),
|
|
195
|
+
fields: z6.array(z6.union([Input.ref, Select.ref, Checkbox.ref])).min(1).max(6),
|
|
196
|
+
submitLabel: z6.string().optional().describe('Padrão "Enviar"')
|
|
197
|
+
})
|
|
198
|
+
});
|
|
199
|
+
// src/components/layout.ts
|
|
200
|
+
import { z as z8 } from "zod";
|
|
201
|
+
|
|
202
|
+
// src/components/text.ts
|
|
203
|
+
import { z as z7 } from "zod";
|
|
204
|
+
var Markdown = defineUiComponent({
|
|
205
|
+
name: "Markdown",
|
|
206
|
+
description: "Texto em markdown. Use para qualquer prose dentro de um widget; suporta citações cite:// e links vault://.",
|
|
207
|
+
props: z7.object({
|
|
208
|
+
text: z7.string().describe("Conteúdo em markdown")
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
var Callout = defineUiComponent({
|
|
212
|
+
name: "Callout",
|
|
213
|
+
description: "Aviso destacado com tom. Use para alertas, prazos, ressalvas ou conclusões importantes.",
|
|
214
|
+
props: z7.object({
|
|
215
|
+
text: z7.string().describe("Conteúdo em markdown"),
|
|
216
|
+
tone: z7.enum(["info", "success", "warning", "error"]).optional().describe("Tom visual, padrão info"),
|
|
217
|
+
title: z7.string().optional()
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// src/components/layout.ts
|
|
222
|
+
var blockRef = z8.union([Markdown.ref, Callout.ref, Metric.ref, Table.ref, BarChart.ref, Buttons.ref, Form.ref, Questions.ref, FileCard.ref, Image.ref, Citation.ref]);
|
|
223
|
+
var Card = defineUiComponent({
|
|
224
|
+
name: "Card",
|
|
225
|
+
description: "Cartão com borda que agrupa blocos relacionados. Não aninhe Card em Card.",
|
|
226
|
+
props: z8.object({
|
|
227
|
+
children: z8.array(blockRef).min(1),
|
|
228
|
+
title: z8.string().optional()
|
|
229
|
+
})
|
|
230
|
+
});
|
|
231
|
+
var Stack = defineUiComponent({
|
|
232
|
+
name: "Stack",
|
|
233
|
+
description: "Raiz do widget. Empilha blocos em coluna ou os distribui em linha.",
|
|
234
|
+
props: z8.object({
|
|
235
|
+
children: z8.array(z8.union([Card.ref, ...blockRef.options])).min(1),
|
|
236
|
+
direction: z8.enum(["column", "row"]).optional().describe("row só para Metric ou Card lado a lado")
|
|
237
|
+
})
|
|
238
|
+
});
|
|
239
|
+
var components = { Stack, Card, Markdown, Callout, Metric, Col, Table, Bar, BarChart, Button, Buttons, Input, Select, Checkbox, Form, Option, Question, Questions, FileCard, Image, Citation };
|
|
240
|
+
// src/library.ts
|
|
241
|
+
import { createLibrary } from "@openuidev/lang-core";
|
|
242
|
+
var library = createLibrary({
|
|
243
|
+
root: "Stack",
|
|
244
|
+
components: Object.values(components),
|
|
245
|
+
actions: [CopyText],
|
|
246
|
+
componentGroups: [
|
|
247
|
+
{
|
|
248
|
+
name: "Layout",
|
|
249
|
+
components: ["Stack", "Card"],
|
|
250
|
+
notes: [
|
|
251
|
+
"- root é sempre Stack. Use Card só quando houver mais de um bloco relacionado.",
|
|
252
|
+
'- direction "row" apenas para Metric ou Card lado a lado, no máximo 3.'
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "Texto",
|
|
257
|
+
components: ["Markdown", "Callout"],
|
|
258
|
+
notes: [
|
|
259
|
+
"- Citações cite:// e links vault:// aparecem no markdown, em Markdown/Callout, ou nos props ref de Citation e vaultRef de FileCard. Nunca em labels, títulos ou valores de tabela."
|
|
260
|
+
]
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "Dados",
|
|
264
|
+
components: ["Metric", "Table", "Col", "BarChart", "Bar"],
|
|
265
|
+
notes: [
|
|
266
|
+
"- Table para 3 ou mais linhas comparáveis. Menos que isso, use markdown fora do widget.",
|
|
267
|
+
"- Cada Col tem exatamente o mesmo número de values. Declare cada Col como statement próprio.",
|
|
268
|
+
"- Metric só para valores isolados e já formatados. Nunca repita um valor da tabela como Metric.",
|
|
269
|
+
"- BarChart só para valores numéricos da mesma unidade; Bar.value é número puro e Bar.display é o texto formatado."
|
|
270
|
+
]
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: "Documentos",
|
|
274
|
+
components: ["FileCard", "Image", "Citation"],
|
|
275
|
+
notes: [
|
|
276
|
+
"- Use FileCard para um arquivo gerado ou recebido que o usuário deve abrir, e Image para uma imagem que deve aparecer na resposta. Nunca invente referências vault://, cite:// ou URLs."
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: "Perguntas e formulários",
|
|
281
|
+
components: ["Questions", "Question", "Option", "Form", "Input", "Select", "Checkbox"],
|
|
282
|
+
notes: [
|
|
283
|
+
"- Questions quando a resposta depender de uma escolha do usuário. Pergunte o mínimo; não pergunte o que já foi dito.",
|
|
284
|
+
"- Form quando faltarem dados concretos (datas, valores, nomes). Nunca combine Form e Questions no mesmo widget.",
|
|
285
|
+
"- Depois de um Questions ou Form, encerre a resposta: não responda o que ainda depende da resposta do usuário."
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "Ações",
|
|
290
|
+
components: ["Buttons", "Button"],
|
|
291
|
+
notes: [
|
|
292
|
+
'- Use Buttons para oferecer próximos passos concretos. Cada Button com @ToAssistant("mensagem") escrita na primeira pessoa, como o usuário diria.',
|
|
293
|
+
'- @CopyText("texto") para copiar um valor pronto, como um número de processo ou um trecho de petição.',
|
|
294
|
+
'- No máximo um Button com variant "primary".'
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
});
|
|
299
|
+
var componentNames = Object.keys(library.components);
|
|
300
|
+
// src/profiles.ts
|
|
301
|
+
import { createLibrary as createLibrary2 } from "@openuidev/lang-core";
|
|
302
|
+
|
|
303
|
+
// src/version.ts
|
|
304
|
+
var LIBRARY_VERSION = "0.4.0";
|
|
305
|
+
var UI_LANG_FENCE = "openui-lang";
|
|
306
|
+
|
|
307
|
+
// src/profiles.ts
|
|
308
|
+
var UI_PROFILES = ["web", "slack"];
|
|
309
|
+
var SLACK_UNSUPPORTED = ["BarChart", "Bar", "Form", "Input", "Select", "Checkbox", "Questions", "Question", "Option", "FileCard", "Image", "Citation"];
|
|
310
|
+
var slackLibrary = createLibrary2({
|
|
311
|
+
root: "Stack",
|
|
312
|
+
components: Object.values(library.extend({ components: { remove: SLACK_UNSUPPORTED } }).components),
|
|
313
|
+
actions: [CopyText],
|
|
314
|
+
componentGroups: [
|
|
315
|
+
{
|
|
316
|
+
name: "Layout",
|
|
317
|
+
components: ["Stack", "Card"],
|
|
318
|
+
notes: [
|
|
319
|
+
"- root é sempre Stack. Use Card só quando houver mais de um bloco relacionado; o título do Card vira um cabeçalho.",
|
|
320
|
+
'- direction "row" apenas para Metric lado a lado, no máximo 3.'
|
|
321
|
+
]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "Texto",
|
|
325
|
+
components: ["Markdown", "Callout"],
|
|
326
|
+
notes: [
|
|
327
|
+
"- Citações cite:// e links vault:// ficam no markdown fora do bloco ou em Markdown/Callout. Nunca em labels, títulos ou valores de tabela."
|
|
328
|
+
]
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "Dados",
|
|
332
|
+
components: ["Metric", "Table", "Col"],
|
|
333
|
+
notes: [
|
|
334
|
+
"- Table para 3 ou mais linhas comparáveis, no máximo uma por resposta. Menos que isso, use markdown fora do widget.",
|
|
335
|
+
"- Cada Col tem exatamente o mesmo número de values. Declare cada Col como statement próprio.",
|
|
336
|
+
"- Metric só para valores isolados e já formatados. Nunca repita um valor da tabela como Metric."
|
|
337
|
+
]
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "Ações",
|
|
341
|
+
components: ["Buttons", "Button"],
|
|
342
|
+
notes: [
|
|
343
|
+
'- Para pedir uma escolha, use Buttons com um Button por alternativa, cada um com @ToAssistant("mensagem") na primeira pessoa. Não há formulários nem perguntas de múltipla escolha.',
|
|
344
|
+
"- Depois de oferecer uma escolha, encerre a resposta: não responda o que ainda depende dela.",
|
|
345
|
+
'- @CopyText("texto") mostra um valor pronto para o usuário copiar, como um número de processo.',
|
|
346
|
+
'- No máximo um Button com variant "primary".'
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
]
|
|
350
|
+
});
|
|
351
|
+
var WEB_EXAMPLES = [
|
|
352
|
+
[
|
|
353
|
+
'Pergunta: "compare os prazos de recurso". Resposta com widget:',
|
|
354
|
+
"",
|
|
355
|
+
"Os prazos variam por tipo de recurso:",
|
|
356
|
+
"",
|
|
357
|
+
`\`\`\`${UI_LANG_FENCE}`,
|
|
358
|
+
"root = Stack([tbl, next])",
|
|
359
|
+
'tbl = Table([tipo, prazo, base], "Prazos recursais")',
|
|
360
|
+
'tipo = Col("Recurso", ["Apelação", "Agravo", "Embargos"])',
|
|
361
|
+
'prazo = Col("Prazo", ["15 dias", "15 dias", "5 dias"], "right")',
|
|
362
|
+
'base = Col("Base legal", ["CPC art. 1.003", "CPC art. 1.003", "CPC art. 1.023"])',
|
|
363
|
+
"next = Buttons([b1, b2])",
|
|
364
|
+
'b1 = Button("Detalhar a apelação", @ToAssistant("Detalhe os requisitos da apelação"), "primary")',
|
|
365
|
+
'b2 = Button("Ver contagem em dias úteis", @ToAssistant("Como conto esses prazos em dias úteis?"))',
|
|
366
|
+
"```",
|
|
367
|
+
"",
|
|
368
|
+
"Todos contados em dias úteis [cite://doc/123#p=4]."
|
|
369
|
+
].join(`
|
|
370
|
+
`)
|
|
371
|
+
];
|
|
372
|
+
var SLACK_EXAMPLES = [
|
|
373
|
+
[
|
|
374
|
+
'Pergunta: "qual recurso cabe contra a sentença?". Resposta com widget:',
|
|
375
|
+
"",
|
|
376
|
+
"Depende do que você quer contestar:",
|
|
377
|
+
"",
|
|
378
|
+
`\`\`\`${UI_LANG_FENCE}`,
|
|
379
|
+
"root = Stack([prazo, opcoes])",
|
|
380
|
+
'prazo = Callout("Os dois recursos contam o prazo em dias úteis a partir da intimação.", "warning", "Prazo")',
|
|
381
|
+
"opcoes = Buttons([a, b])",
|
|
382
|
+
'a = Button("Rever o mérito", @ToAssistant("Quero rever o mérito da sentença"), "primary")',
|
|
383
|
+
'b = Button("Esclarecer a decisão", @ToAssistant("Quero só esclarecer um ponto da sentença"))',
|
|
384
|
+
"```"
|
|
385
|
+
].join(`
|
|
386
|
+
`)
|
|
387
|
+
];
|
|
388
|
+
var profiles = {
|
|
389
|
+
web: { library, rules: [], examples: WEB_EXAMPLES },
|
|
390
|
+
slack: {
|
|
391
|
+
library: slackLibrary,
|
|
392
|
+
rules: ["A conversa acontece no Slack: prefira respostas curtas e no máximo um widget por resposta."],
|
|
393
|
+
examples: SLACK_EXAMPLES
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
// src/prompt.ts
|
|
397
|
+
var PREAMBLE = `## Widgets na resposta
|
|
398
|
+
|
|
399
|
+
Você responde em markdown normal. Quando parte da resposta ficar melhor como interface (tabela comparativa, números de destaque, aviso, próximos passos clicáveis), insira um bloco de código com a linguagem \`${UI_LANG_FENCE}\` contendo um programa OpenUI Lang. O bloco é renderizado como interface nativa no lugar do código; o texto fora do bloco continua markdown.
|
|
400
|
+
|
|
401
|
+
Regras do envelope:
|
|
402
|
+
- Zero ou mais blocos \`${UI_LANG_FENCE}\` por resposta, cada um com seu próprio \`root = Stack(...)\` na primeira linha.
|
|
403
|
+
- Prose, citações cite:// e links vault:// ficam no markdown fora do bloco, ou dentro de Markdown/Callout.
|
|
404
|
+
- Resposta curta ou puramente textual: nenhum bloco. A maioria das respostas não precisa de widget.
|
|
405
|
+
- Nunca repita no markdown o que já está no widget, nem o contrário.
|
|
406
|
+
- Dentro do bloco só OpenUI Lang: sem comentários, sem markdown solto, sem fences aninhados.`;
|
|
407
|
+
var defaultPrompts = new Map;
|
|
408
|
+
function prompt(options) {
|
|
409
|
+
if (options.additionalRules?.length)
|
|
410
|
+
return buildPrompt(options.profile, options.additionalRules);
|
|
411
|
+
let built = defaultPrompts.get(options.profile);
|
|
412
|
+
if (built === undefined) {
|
|
413
|
+
built = buildPrompt(options.profile, []);
|
|
414
|
+
defaultPrompts.set(options.profile, built);
|
|
415
|
+
}
|
|
416
|
+
return built;
|
|
417
|
+
}
|
|
418
|
+
function buildPrompt(profile, additionalRules) {
|
|
419
|
+
const definition = profiles[profile];
|
|
420
|
+
const rules = [...definition.rules, ...additionalRules];
|
|
421
|
+
return definition.library.prompt({
|
|
422
|
+
preamble: PREAMBLE,
|
|
423
|
+
examples: definition.examples,
|
|
424
|
+
inlineMode: false,
|
|
425
|
+
toolCalls: false,
|
|
426
|
+
bindings: false,
|
|
427
|
+
builtinFunctions: false,
|
|
428
|
+
...rules.length ? { additionalRules: rules } : {}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
// src/safe-url.ts
|
|
432
|
+
var SAFE_PROTOCOLS = new Set(["https:", "http:", "mailto:"]);
|
|
433
|
+
function isSafeUrl(url) {
|
|
434
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(url))
|
|
435
|
+
return false;
|
|
436
|
+
try {
|
|
437
|
+
return SAFE_PROTOCOLS.has(new URL(url).protocol);
|
|
438
|
+
} catch {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// src/segments.ts
|
|
443
|
+
var WIDGET_PENDING_TEXT = "Montando um widget…";
|
|
444
|
+
var WIDGET_OPENER = new RegExp(`^[ \\t]{0,3}(\`\`\`|~~~)${UI_LANG_FENCE}(?![\\w-])`);
|
|
445
|
+
var OPENER_TEXTS = [`\`\`\`${UI_LANG_FENCE}`, `~~~${UI_LANG_FENCE}`];
|
|
446
|
+
var FENCE_OPENER = /^[ \t]{0,3}(`{3,}|~{3,})([^\n]*)$/;
|
|
447
|
+
var FENCE_CLOSER = /^[ \t]{0,3}(`{3,}|~{3,})[ \t]*\r?$/;
|
|
448
|
+
function openingFence(line) {
|
|
449
|
+
const match = FENCE_OPENER.exec(line);
|
|
450
|
+
if (!match || match[1][0] === "`" && match[2].includes("`"))
|
|
451
|
+
return null;
|
|
452
|
+
return { character: match[1][0], length: match[1].length };
|
|
453
|
+
}
|
|
454
|
+
function closesFence(line, fence) {
|
|
455
|
+
const match = FENCE_CLOSER.exec(line);
|
|
456
|
+
return !!match && match[1][0] === fence.character && match[1].length >= fence.length;
|
|
457
|
+
}
|
|
458
|
+
function closingLine(lines, opener, fence) {
|
|
459
|
+
const closer = fence === "```" ? /^[ \t]{0,3}```[ \t]*\r?$/ : /^[ \t]{0,3}~~~[ \t]*\r?$/;
|
|
460
|
+
for (let index = opener + 1;index < lines.length; index++) {
|
|
461
|
+
if (closer.test(lines[index]))
|
|
462
|
+
return index;
|
|
463
|
+
}
|
|
464
|
+
return -1;
|
|
465
|
+
}
|
|
466
|
+
function splitAnswer(content) {
|
|
467
|
+
const lines = content.split(`
|
|
468
|
+
`);
|
|
469
|
+
const segments = [];
|
|
470
|
+
let markdown = [];
|
|
471
|
+
const flush = () => {
|
|
472
|
+
const text = markdown.join(`
|
|
473
|
+
`).trim();
|
|
474
|
+
if (text)
|
|
475
|
+
segments.push({ kind: "markdown", text });
|
|
476
|
+
markdown = [];
|
|
477
|
+
};
|
|
478
|
+
let fence = null;
|
|
479
|
+
let index = 0;
|
|
480
|
+
while (index < lines.length) {
|
|
481
|
+
const line = lines[index];
|
|
482
|
+
if (fence) {
|
|
483
|
+
fence = closesFence(line, fence) ? null : fence;
|
|
484
|
+
markdown.push(line);
|
|
485
|
+
index++;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const opener = WIDGET_OPENER.exec(line);
|
|
489
|
+
if (!opener) {
|
|
490
|
+
fence = openingFence(line);
|
|
491
|
+
markdown.push(line);
|
|
492
|
+
index++;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
flush();
|
|
496
|
+
const end = closingLine(lines, index, opener[1]);
|
|
497
|
+
segments.push({ kind: "widget", code: lines.slice(index + 1, end === -1 ? lines.length : end).join(`
|
|
498
|
+
`), complete: end !== -1 });
|
|
499
|
+
index = end === -1 ? lines.length : end + 1;
|
|
500
|
+
}
|
|
501
|
+
flush();
|
|
502
|
+
return segments;
|
|
503
|
+
}
|
|
504
|
+
function mayOpenWidget(line) {
|
|
505
|
+
const rest = line.replace(/^[ \t]{0,3}/, "");
|
|
506
|
+
return line.length > 0 && (WIDGET_OPENER.test(line) || OPENER_TEXTS.some((opener) => opener.startsWith(rest)));
|
|
507
|
+
}
|
|
508
|
+
function maskWidgets(content, placeholder = WIDGET_PENDING_TEXT) {
|
|
509
|
+
const lines = content.split(`
|
|
510
|
+
`);
|
|
511
|
+
const shown = [];
|
|
512
|
+
let fence = null;
|
|
513
|
+
let index = 0;
|
|
514
|
+
while (index < lines.length) {
|
|
515
|
+
const line = lines[index];
|
|
516
|
+
if (fence) {
|
|
517
|
+
fence = closesFence(line, fence) ? null : fence;
|
|
518
|
+
shown.push(line);
|
|
519
|
+
index++;
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (index === lines.length - 1 && mayOpenWidget(line))
|
|
523
|
+
return shown.length ? `${shown.join(`
|
|
524
|
+
`)}
|
|
525
|
+
` : "";
|
|
526
|
+
const opener = WIDGET_OPENER.exec(line);
|
|
527
|
+
if (opener) {
|
|
528
|
+
shown.push(placeholder);
|
|
529
|
+
const end = closingLine(lines, index, opener[1]);
|
|
530
|
+
if (end === -1)
|
|
531
|
+
return shown.join(`
|
|
532
|
+
`);
|
|
533
|
+
index = end + 1;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
fence = openingFence(line);
|
|
537
|
+
shown.push(line);
|
|
538
|
+
index++;
|
|
539
|
+
}
|
|
540
|
+
return shown.join(`
|
|
541
|
+
`);
|
|
542
|
+
}
|
|
543
|
+
// src/slack.ts
|
|
544
|
+
import { createParser, evaluateElementProps } from "@openuidev/lang-core";
|
|
545
|
+
import { z as z9 } from "zod";
|
|
546
|
+
|
|
547
|
+
// src/widget-context.ts
|
|
548
|
+
var WIDGET_CONTEXT_LIMIT = 2000;
|
|
549
|
+
function formatWidgetContext(context, options = {}) {
|
|
550
|
+
if (context === undefined || context === null)
|
|
551
|
+
return "";
|
|
552
|
+
const limit = options.limit ?? WIDGET_CONTEXT_LIMIT;
|
|
553
|
+
let json;
|
|
554
|
+
try {
|
|
555
|
+
json = JSON.stringify(context).replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
|
|
556
|
+
} catch (error) {
|
|
557
|
+
options.onDrop?.({ reason: "not-serialisable", error: error instanceof Error ? error.message : String(error) });
|
|
558
|
+
return "";
|
|
559
|
+
}
|
|
560
|
+
if (json.length > limit) {
|
|
561
|
+
options.onDrop?.({ reason: "too-large", length: json.length, limit });
|
|
562
|
+
return "";
|
|
563
|
+
}
|
|
564
|
+
return `
|
|
565
|
+
|
|
566
|
+
<widget_context>${json}</widget_context>`;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/slack.ts
|
|
570
|
+
var WIDGET_FAILED_TEXT = "Não foi possível mostrar um widget desta resposta.";
|
|
571
|
+
var CALLOUT_LABELS = { info: "Nota", success: "Concluído", warning: "Atenção", error: "Erro" };
|
|
572
|
+
var BUTTON_TEXT_LIMIT = 75;
|
|
573
|
+
var BUTTON_VALUE_LIMIT = 2000;
|
|
574
|
+
var BUTTON_URL_LIMIT = 3000;
|
|
575
|
+
var HEADER_TEXT_LIMIT = 150;
|
|
576
|
+
var SECTION_TEXT_LIMIT = 3000;
|
|
577
|
+
var SECTION_FIELDS_LIMIT = 10;
|
|
578
|
+
var FIELD_TEXT_LIMIT = 2000;
|
|
579
|
+
var TABLE_ROWS_LIMIT = 100;
|
|
580
|
+
var TABLE_COLUMNS_LIMIT = 20;
|
|
581
|
+
var TABLE_CHARACTERS_LIMIT = 1e4;
|
|
582
|
+
var schema = library.toJSONSchema();
|
|
583
|
+
var parser = createParser(schema, library.root);
|
|
584
|
+
function isSlackWidgetSendAction(actionId) {
|
|
585
|
+
return /^openui_send_\d{1,3}$/.test(actionId);
|
|
586
|
+
}
|
|
587
|
+
function isSlackWidgetLinkAction(actionId) {
|
|
588
|
+
return /^openui_link_\d{1,3}$/.test(actionId);
|
|
589
|
+
}
|
|
590
|
+
var actionValueSchema = z9.object({
|
|
591
|
+
t: z9.string().regex(/^\d+\.\d+$/),
|
|
592
|
+
m: z9.string().min(1)
|
|
593
|
+
});
|
|
594
|
+
function parseSlackWidgetAction(value) {
|
|
595
|
+
let decoded;
|
|
596
|
+
try {
|
|
597
|
+
decoded = JSON.parse(value);
|
|
598
|
+
} catch {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
const parsed = actionValueSchema.safeParse(decoded);
|
|
602
|
+
return parsed.success ? { threadTs: parsed.data.t, message: parsed.data.m } : null;
|
|
603
|
+
}
|
|
604
|
+
function renderSlackAnswer(content, options) {
|
|
605
|
+
const segments = splitAnswer(content);
|
|
606
|
+
if (!segments.some((segment) => segment.kind === "widget"))
|
|
607
|
+
return null;
|
|
608
|
+
const state = { options, buttons: 0, tables: 0 };
|
|
609
|
+
const rendered = join(segments.map((segment) => segment.kind === "markdown" ? prose(segment.text, state) : widget(segment.code, segment.complete, state)));
|
|
610
|
+
return { text: rendered.text, blocks: fitting(rendered.blocks, options) ?? fitting(options.prose(rendered.text), options) ?? [] };
|
|
611
|
+
}
|
|
612
|
+
function fitting(blocks, options) {
|
|
613
|
+
return blocks.length <= options.maxBlocks ? blocks : null;
|
|
614
|
+
}
|
|
615
|
+
function widget(code, complete, state) {
|
|
616
|
+
if (!complete)
|
|
617
|
+
return note(WIDGET_PENDING_TEXT);
|
|
618
|
+
const root = evaluate(code);
|
|
619
|
+
const rendered = root ? node(root, state) : null;
|
|
620
|
+
return rendered?.blocks.length ? rendered : note(WIDGET_FAILED_TEXT);
|
|
621
|
+
}
|
|
622
|
+
function evaluate(code) {
|
|
623
|
+
try {
|
|
624
|
+
const result = parser.parse(code);
|
|
625
|
+
if (!result.root)
|
|
626
|
+
return null;
|
|
627
|
+
return evaluateElementProps(result.root, {
|
|
628
|
+
ctx: { getState: () => null, resolveRef: () => null, actions: schema.actions },
|
|
629
|
+
library,
|
|
630
|
+
store: null
|
|
631
|
+
});
|
|
632
|
+
} catch {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function isElement(value) {
|
|
637
|
+
return typeof value === "object" && value !== null && value.type === "element";
|
|
638
|
+
}
|
|
639
|
+
function children(value, typeName) {
|
|
640
|
+
return (Array.isArray(value) ? value : []).filter((item) => isElement(item) && (!typeName || item.typeName === typeName));
|
|
641
|
+
}
|
|
642
|
+
function text(value) {
|
|
643
|
+
return typeof value === "string" ? value : typeof value === "number" ? String(value) : "";
|
|
644
|
+
}
|
|
645
|
+
function limit(value, max) {
|
|
646
|
+
let result = "";
|
|
647
|
+
for (const character of value) {
|
|
648
|
+
if (result.length + character.length > max)
|
|
649
|
+
break;
|
|
650
|
+
result += character;
|
|
651
|
+
}
|
|
652
|
+
return result;
|
|
653
|
+
}
|
|
654
|
+
function escapeMrkdwn(value) {
|
|
655
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
656
|
+
}
|
|
657
|
+
function join(parts) {
|
|
658
|
+
return { text: parts.map((part) => part.text).filter(Boolean).join(`
|
|
659
|
+
|
|
660
|
+
`), blocks: parts.flatMap((part) => part.blocks) };
|
|
661
|
+
}
|
|
662
|
+
function note(value) {
|
|
663
|
+
return { text: value, blocks: [{ type: "context", elements: [{ type: "plain_text", text: value, emoji: false }] }] };
|
|
664
|
+
}
|
|
665
|
+
function prose(value, state) {
|
|
666
|
+
const trimmed = value.trim();
|
|
667
|
+
return trimmed ? { text: trimmed, blocks: state.options.prose(trimmed) } : { text: "", blocks: [] };
|
|
668
|
+
}
|
|
669
|
+
function node(element, state) {
|
|
670
|
+
switch (element.typeName) {
|
|
671
|
+
case "Stack":
|
|
672
|
+
return stack(children(element.props.children), state);
|
|
673
|
+
case "Card":
|
|
674
|
+
return card(element, state);
|
|
675
|
+
case "Markdown":
|
|
676
|
+
return prose(text(element.props.text), state);
|
|
677
|
+
case "Callout":
|
|
678
|
+
return callout(element, state);
|
|
679
|
+
case "Metric":
|
|
680
|
+
return metrics([element]);
|
|
681
|
+
case "Table":
|
|
682
|
+
return table(element, state);
|
|
683
|
+
case "Buttons":
|
|
684
|
+
return buttons(children(element.props.buttons, "Button"), state);
|
|
685
|
+
case "Button":
|
|
686
|
+
return buttons([element], state);
|
|
687
|
+
default:
|
|
688
|
+
return prose(describe(element), state);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function stack(items, state) {
|
|
692
|
+
const parts = [];
|
|
693
|
+
let run = [];
|
|
694
|
+
for (const item of items) {
|
|
695
|
+
if (item.typeName === "Metric") {
|
|
696
|
+
run.push(item);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (run.length) {
|
|
700
|
+
parts.push(metrics(run));
|
|
701
|
+
run = [];
|
|
702
|
+
}
|
|
703
|
+
parts.push(node(item, state));
|
|
704
|
+
}
|
|
705
|
+
if (run.length)
|
|
706
|
+
parts.push(metrics(run));
|
|
707
|
+
return join(parts);
|
|
708
|
+
}
|
|
709
|
+
function card(element, state) {
|
|
710
|
+
const title = text(element.props.title);
|
|
711
|
+
const body = stack(children(element.props.children), state);
|
|
712
|
+
return {
|
|
713
|
+
text: [title, body.text].filter(Boolean).join(`
|
|
714
|
+
|
|
715
|
+
`),
|
|
716
|
+
blocks: [
|
|
717
|
+
...title ? [{ type: "header", text: { type: "plain_text", text: limit(title, HEADER_TEXT_LIMIT), emoji: false } }] : [],
|
|
718
|
+
...body.blocks,
|
|
719
|
+
{ type: "divider" }
|
|
720
|
+
]
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function callout(element, state) {
|
|
724
|
+
const tone = element.props.tone;
|
|
725
|
+
const title = text(element.props.title) || CALLOUT_LABELS[tone === "success" || tone === "warning" || tone === "error" ? tone : "info"];
|
|
726
|
+
const body = text(element.props.text).trim();
|
|
727
|
+
const quoted = [`**${title}**`, ...body.split(`
|
|
728
|
+
`)].map((line) => `> ${line}`).join(`
|
|
729
|
+
`);
|
|
730
|
+
return { text: `${title}: ${body}`, blocks: state.options.prose(quoted) };
|
|
731
|
+
}
|
|
732
|
+
function metrics(items) {
|
|
733
|
+
const fields = items.map((item) => {
|
|
734
|
+
const label = text(item.props.label);
|
|
735
|
+
const delta = text(item.props.delta);
|
|
736
|
+
const value = delta ? `${text(item.props.value)} (${delta})` : text(item.props.value);
|
|
737
|
+
return { line: `${label}: ${value}`, field: { type: "mrkdwn", text: limit(`*${escapeMrkdwn(label)}*
|
|
738
|
+
${escapeMrkdwn(value)}`, FIELD_TEXT_LIMIT) } };
|
|
739
|
+
});
|
|
740
|
+
const blocks = [];
|
|
741
|
+
for (let start = 0;start < fields.length; start += SECTION_FIELDS_LIMIT)
|
|
742
|
+
blocks.push({ type: "section", fields: fields.slice(start, start + SECTION_FIELDS_LIMIT).map((item) => item.field) });
|
|
743
|
+
return { text: fields.map((item) => item.line).join(`
|
|
744
|
+
`), blocks };
|
|
745
|
+
}
|
|
746
|
+
function table(element, state) {
|
|
747
|
+
const columns = children(element.props.columns, "Col").map((column) => ({
|
|
748
|
+
label: text(column.props.label),
|
|
749
|
+
values: (Array.isArray(column.props.values) ? column.props.values : []).map(text),
|
|
750
|
+
align: column.props.align === "right" ? "right" : "left"
|
|
751
|
+
}));
|
|
752
|
+
if (!columns.length)
|
|
753
|
+
return { text: "", blocks: [] };
|
|
754
|
+
const rowCount = Math.max(...columns.map((column) => column.values.length));
|
|
755
|
+
const rows = [columns.map((column) => column.label), ...Array.from({ length: rowCount }, (_, row) => columns.map((column) => column.values[row] ?? ""))];
|
|
756
|
+
const caption = text(element.props.caption);
|
|
757
|
+
const written = [caption, tableMarkdown(columns, rows)].filter(Boolean).join(`
|
|
758
|
+
|
|
759
|
+
`);
|
|
760
|
+
const cells = rows.map((row) => row.map((cell) => cell || "-"));
|
|
761
|
+
const characters = cells.flat().reduce((total, cell) => total + cell.length, 0);
|
|
762
|
+
if (state.tables > 0 || columns.length > TABLE_COLUMNS_LIMIT || rows.length > TABLE_ROWS_LIMIT || characters > TABLE_CHARACTERS_LIMIT)
|
|
763
|
+
return prose(written, state);
|
|
764
|
+
state.tables++;
|
|
765
|
+
return {
|
|
766
|
+
text: written,
|
|
767
|
+
blocks: [
|
|
768
|
+
...caption ? [{ type: "section", text: { type: "plain_text", text: limit(caption, SECTION_TEXT_LIMIT), emoji: false } }] : [],
|
|
769
|
+
{
|
|
770
|
+
type: "table",
|
|
771
|
+
column_settings: columns.map((column) => ({ align: column.align, is_wrapped: true })),
|
|
772
|
+
rows: cells.map((row) => row.map((cell) => ({ type: "raw_text", text: cell })))
|
|
773
|
+
}
|
|
774
|
+
]
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function tableMarkdown(columns, rows) {
|
|
778
|
+
const cell = (value) => value.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
779
|
+
const [header = [], ...body] = rows;
|
|
780
|
+
return [
|
|
781
|
+
`| ${header.map(cell).join(" | ")} |`,
|
|
782
|
+
`| ${columns.map((column) => column.align === "right" ? "---:" : "---").join(" | ")} |`,
|
|
783
|
+
...body.map((row) => `| ${row.map(cell).join(" | ")} |`)
|
|
784
|
+
].join(`
|
|
785
|
+
`);
|
|
786
|
+
}
|
|
787
|
+
function buttonTarget(label, plan) {
|
|
788
|
+
const steps = plan?.steps ?? [];
|
|
789
|
+
if (!steps.length)
|
|
790
|
+
return { kind: "send", message: label, context: "" };
|
|
791
|
+
for (const step of steps) {
|
|
792
|
+
if (step.type === "continue_conversation")
|
|
793
|
+
return { kind: "send", message: step.message, context: formatWidgetContext(step.context) };
|
|
794
|
+
if (step.type === "open_url")
|
|
795
|
+
return /^https?:/i.test(step.url) && isSafeUrl(step.url) ? { kind: "link", url: step.url } : { kind: "none" };
|
|
796
|
+
if (step.type === "custom_action" && step.name === "CopyText" && typeof step.params.text === "string")
|
|
797
|
+
return { kind: "copy", text: step.params.text };
|
|
798
|
+
}
|
|
799
|
+
return { kind: "none" };
|
|
800
|
+
}
|
|
801
|
+
function buttonValue(threadTs, message) {
|
|
802
|
+
const value = JSON.stringify({ t: threadTs, m: message });
|
|
803
|
+
return value.length <= BUTTON_VALUE_LIMIT ? value : null;
|
|
804
|
+
}
|
|
805
|
+
function buttons(items, state) {
|
|
806
|
+
const elements = [];
|
|
807
|
+
const copies = [];
|
|
808
|
+
const plain = [];
|
|
809
|
+
const lines = [];
|
|
810
|
+
for (const item of items) {
|
|
811
|
+
const label = text(item.props.label);
|
|
812
|
+
if (!label)
|
|
813
|
+
continue;
|
|
814
|
+
const target = buttonTarget(label, item.props.action);
|
|
815
|
+
const buttonText = { type: "plain_text", text: limit(label, BUTTON_TEXT_LIMIT), emoji: false };
|
|
816
|
+
const style = item.props.variant === "primary" ? { style: "primary" } : {};
|
|
817
|
+
if (target.kind === "send") {
|
|
818
|
+
const threadTs = state.options.threadTs;
|
|
819
|
+
const value = threadTs ? buttonValue(threadTs, `${target.message}${target.context}`) : null;
|
|
820
|
+
lines.push(`• ${label}`);
|
|
821
|
+
if (value)
|
|
822
|
+
elements.push({ type: "button", action_id: `openui_send_${state.buttons++}`, text: buttonText, value, ...style });
|
|
823
|
+
else
|
|
824
|
+
plain.push(`• ${label}`);
|
|
825
|
+
} else if (target.kind === "link") {
|
|
826
|
+
lines.push(`• ${label}: ${target.url}`);
|
|
827
|
+
if (target.url.length <= BUTTON_URL_LIMIT)
|
|
828
|
+
elements.push({ type: "button", action_id: `openui_link_${state.buttons++}`, text: buttonText, url: target.url, ...style });
|
|
829
|
+
else
|
|
830
|
+
plain.push(`• ${label}: ${target.url}`);
|
|
831
|
+
} else if (target.kind === "copy") {
|
|
832
|
+
lines.push(`• ${label}: ${target.text}`);
|
|
833
|
+
const section = `*${escapeMrkdwn(label)}*
|
|
834
|
+
\`${escapeMrkdwn(target.text)}\``;
|
|
835
|
+
if (section.length <= SECTION_TEXT_LIMIT)
|
|
836
|
+
copies.push({ type: "section", text: { type: "mrkdwn", text: section } });
|
|
837
|
+
else
|
|
838
|
+
plain.push(`• ${label}: ${target.text}`);
|
|
839
|
+
} else {
|
|
840
|
+
lines.push(`• ${label}`);
|
|
841
|
+
plain.push(`• ${label}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
text: lines.join(`
|
|
846
|
+
`),
|
|
847
|
+
blocks: [
|
|
848
|
+
...elements.length ? [{ type: "actions", elements }] : [],
|
|
849
|
+
...copies,
|
|
850
|
+
...plain.length ? state.options.prose(plain.join(`
|
|
851
|
+
`)) : []
|
|
852
|
+
]
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function markdownLink(label, target, scheme) {
|
|
856
|
+
return scheme.test(target) && /^[^\s<>"'()]+$/.test(target) ? `[${label.replace(/[\\[\]]/g, "\\$&")}](${target})` : label;
|
|
857
|
+
}
|
|
858
|
+
function describe(element) {
|
|
859
|
+
const props = element.props;
|
|
860
|
+
switch (element.typeName) {
|
|
861
|
+
case "BarChart":
|
|
862
|
+
return [`**${text(props.title)}**`, ...children(props.bars, "Bar").map((bar) => `- ${text(bar.props.label)}: ${text(bar.props.display) || text(bar.props.value)}`)].join(`
|
|
863
|
+
`);
|
|
864
|
+
case "Questions":
|
|
865
|
+
return [
|
|
866
|
+
...children(props.questions, "Question").map((question) => [`**${text(question.props.title)}**`, ...children(question.props.options, "Option").map((option) => `- ${text(option.props.label)}`)].join(`
|
|
867
|
+
`)),
|
|
868
|
+
"Responda nesta conversa."
|
|
869
|
+
].join(`
|
|
870
|
+
|
|
871
|
+
`);
|
|
872
|
+
case "Form": {
|
|
873
|
+
const fields = children(props.fields).map((field) => {
|
|
874
|
+
const options = field.typeName === "Select" && Array.isArray(field.props.options) ? field.props.options.map(text).filter(Boolean) : [];
|
|
875
|
+
return options.length ? `${text(field.props.label)} (${options.join(", ")})` : text(field.props.label);
|
|
876
|
+
}).filter(Boolean);
|
|
877
|
+
return fields.length ? `Para continuar, responda com: ${fields.join(", ")}.` : "";
|
|
878
|
+
}
|
|
879
|
+
case "FileCard":
|
|
880
|
+
return markdownLink(text(props.name), text(props.vaultRef), /^vault:\/\//);
|
|
881
|
+
case "Image":
|
|
882
|
+
return markdownLink(text(props.alt) || "Imagem", text(props.src), /^(?:https:|vault:)\/\//);
|
|
883
|
+
case "Citation":
|
|
884
|
+
return markdownLink(text(props.label) || "fonte", text(props.ref), /^cite:\/\//);
|
|
885
|
+
case "Col":
|
|
886
|
+
return [`**${text(props.label)}**`, ...(Array.isArray(props.values) ? props.values : []).map((value) => `- ${text(value)}`)].join(`
|
|
887
|
+
`);
|
|
888
|
+
default:
|
|
889
|
+
return "";
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
export {
|
|
893
|
+
Bar,
|
|
894
|
+
BarChart,
|
|
895
|
+
Button,
|
|
896
|
+
Buttons,
|
|
897
|
+
Callout,
|
|
898
|
+
Card,
|
|
899
|
+
Checkbox,
|
|
900
|
+
Citation,
|
|
901
|
+
Col,
|
|
902
|
+
CopyText,
|
|
903
|
+
FileCard,
|
|
904
|
+
Form,
|
|
905
|
+
Image,
|
|
906
|
+
Input,
|
|
907
|
+
LIBRARY_VERSION,
|
|
908
|
+
Markdown,
|
|
909
|
+
Metric,
|
|
910
|
+
Option,
|
|
911
|
+
Question,
|
|
912
|
+
Questions,
|
|
913
|
+
Select,
|
|
914
|
+
Stack,
|
|
915
|
+
Table,
|
|
916
|
+
UI_LANG_FENCE,
|
|
917
|
+
UI_PROFILES,
|
|
918
|
+
WIDGET_PENDING_TEXT,
|
|
919
|
+
actions,
|
|
920
|
+
componentNames,
|
|
921
|
+
components,
|
|
922
|
+
defineUiComponent,
|
|
923
|
+
formatQuestionAnswers,
|
|
924
|
+
formatWidgetContext,
|
|
925
|
+
isSafeUrl,
|
|
926
|
+
isSlackWidgetLinkAction,
|
|
927
|
+
isSlackWidgetSendAction,
|
|
928
|
+
library,
|
|
929
|
+
maskWidgets,
|
|
930
|
+
parseSlackWidgetAction,
|
|
931
|
+
profiles,
|
|
932
|
+
prompt,
|
|
933
|
+
renderSlackAnswer,
|
|
934
|
+
splitAnswer
|
|
935
|
+
};
|