@kyyinfinite/lumina 1.0.0 → 1.0.1
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/package.json +1 -1
- package/src/builders/ai-rich.js +0 -165
- package/src/builders/base.js +4 -51
- package/src/builders/button-v2.js +13 -78
- package/src/builders/button.js +20 -234
- package/src/builders/card.js +9 -76
- package/src/builders/carousel.js +4 -61
- package/src/builders/index.js +0 -7
- package/src/client/bot.js +29 -160
- package/src/client/connection.js +4 -111
- package/src/errors.js +0 -37
- package/src/index.d.ts +1 -28
- package/src/index.js +20 -121
- package/src/media/fetch.js +2 -33
- package/src/media/image.js +1 -41
- package/src/media/resolver.js +3 -55
- package/src/media/uploader.js +0 -30
- package/src/media/video.js +1 -39
- package/src/parsers/code-tokenizer-keywords.js +0 -12
- package/src/parsers/code-tokenizer.js +0 -42
- package/src/parsers/index.js +0 -7
- package/src/parsers/inline-entity.js +8 -117
- package/src/parsers/table-metadata.js +1 -35
- package/src/proto/enums.js +9 -65
- package/src/proto/layouts.js +3 -64
- package/src/proto/primitives.js +4 -91
- package/src/proto/relay-nodes.js +1 -32
- package/src/proto/rich-response.js +6 -57
- package/src/proto/updater.js +0 -85
- package/src/services/index.js +0 -7
- package/src/services/media-service.js +1 -102
- package/src/services/message-service.js +16 -158
- package/src/services/proto-service.js +3 -57
- package/src/utils/id.js +0 -25
- package/src/utils/logger.js +2 -39
- package/src/utils/mime.js +17 -73
- package/src/utils/promise.js +0 -26
- package/src/utils/validator.js +6 -71
|
@@ -1,42 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file inline-entity.js
|
|
3
|
-
* @module lumina/parsers/inline-entity
|
|
4
|
-
*
|
|
5
|
-
* extractInlineEntities — parses three markdown-like inline syntaxes inside
|
|
6
|
-
* AI-rich text content and returns the rewritten text plus proto-ready
|
|
7
|
-
* inline-entity metadata.
|
|
8
|
-
*
|
|
9
|
-
* Supported syntaxes:
|
|
10
|
-
*
|
|
11
|
-
* [text](url) → hyperlink (untrusted if `url` starts with `!`)
|
|
12
|
-
* [](url) → citation (renders as a numeric superscript)
|
|
13
|
-
* [text]<url|w|h|fh|p> → latex (math image with dimensions)
|
|
14
|
-
*
|
|
15
|
-
* The rewritten text replaces each `[...]` span with `{{KEY}}...{{/KEY}}`
|
|
16
|
-
* placeholders that WhatsApp's rich-response renderer substitutes back with
|
|
17
|
-
* the entity's interactive UI element.
|
|
18
|
-
*
|
|
19
|
-
* This is the modernised, de-obfuscated successor to the legacy `extractIE`
|
|
20
|
-
* function in `_build-m.js`. Key changes:
|
|
21
|
-
*
|
|
22
|
-
* 1. The `NIXEL_` prefix (obfuscated as `\u004E\u0049\u0058\u0045\u004C_`)
|
|
23
|
-
* is replaced by the configurable `LUMINA_` prefix.
|
|
24
|
-
* 2. The off-by-one bug on citation keys is fixed: every counter is now
|
|
25
|
-
* zero-based and keys match `reference_id` consistently.
|
|
26
|
-
* 3. Magic numbers (font_height, padding, default width/height) are pulled
|
|
27
|
-
* into named constants.
|
|
28
|
-
* 4. The `createIE` switch-by-if is replaced by a typed lookup table.
|
|
29
|
-
* 5. Pure function, no side-effects — easily unit-testable.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
1
|
import { entityKey } from '../utils/id.js'
|
|
33
2
|
|
|
34
|
-
/** @typedef {'hyperlink'|'citation'|'latex'} EntityType */
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Default LaTeX rendering parameters. Sourced from the legacy implementation.
|
|
38
|
-
* Adjust if WhatsApp updates its rendering defaults.
|
|
39
|
-
*/
|
|
40
3
|
const LATEX_DEFAULTS = Object.freeze({
|
|
41
4
|
width: 100,
|
|
42
5
|
height: 100,
|
|
@@ -44,11 +7,6 @@ const LATEX_DEFAULTS = Object.freeze({
|
|
|
44
7
|
padding: 15,
|
|
45
8
|
})
|
|
46
9
|
|
|
47
|
-
/**
|
|
48
|
-
* Per-type metadata shaper. Replaces the legacy `createIE` switch.
|
|
49
|
-
*
|
|
50
|
-
* @type {Record<EntityType, (item: any) => object>}
|
|
51
|
-
*/
|
|
52
10
|
const SHAPERS = {
|
|
53
11
|
hyperlink: (item) => ({
|
|
54
12
|
key: item.key,
|
|
@@ -86,76 +44,35 @@ const SHAPERS = {
|
|
|
86
44
|
}),
|
|
87
45
|
}
|
|
88
46
|
|
|
89
|
-
/**
|
|
90
|
-
* @typedef {object} ExtractOptions
|
|
91
|
-
* @property {boolean} [extract=true] Master switch — when false, returns the text unchanged.
|
|
92
|
-
* @property {boolean} [hyperlink=true] Parse `[text](url)` syntax.
|
|
93
|
-
* @property {boolean} [citation=true] Parse `[](url)` syntax.
|
|
94
|
-
* @property {boolean} [latex=true] Parse `[text]<url|w|h|fh|p>` syntax.
|
|
95
|
-
* @property {string} [prefix='LUMINA'] Override the entity-key prefix.
|
|
96
|
-
*/
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @typedef {object} ExtractResult
|
|
100
|
-
* @property {string} text Rewritten text with `{{KEY}}…{{/KEY}}` placeholders.
|
|
101
|
-
* @property {Array<{ type: EntityType, key: string, [k: string]: any }>} entities
|
|
102
|
-
* Raw per-entity descriptors (one per matched span).
|
|
103
|
-
* @property {Array<{ key: string, metadata: object }>} metadata
|
|
104
|
-
* Proto-ready inline-entity metadata, ready to attach to a primitive.
|
|
105
|
-
*/
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Parse inline entities from text.
|
|
109
|
-
*
|
|
110
|
-
* @param {string} text
|
|
111
|
-
* @param {ExtractOptions} [opts]
|
|
112
|
-
* @returns {ExtractResult}
|
|
113
|
-
*/
|
|
114
47
|
export function extractInlineEntities(text, opts = {}) {
|
|
115
|
-
const {
|
|
116
|
-
extract = true,
|
|
117
|
-
hyperlink = true,
|
|
118
|
-
citation = true,
|
|
119
|
-
latex = true,
|
|
120
|
-
prefix = 'LUMINA',
|
|
121
|
-
} = opts
|
|
48
|
+
const { extract = true, hyperlink = true, citation = true, latex = true } = opts
|
|
122
49
|
|
|
123
50
|
if (!extract || typeof text !== 'string' || text.length === 0) {
|
|
124
51
|
return { text: text ?? '', entities: [], metadata: [] }
|
|
125
52
|
}
|
|
126
53
|
|
|
127
|
-
/** @type {Array<{ type: EntityType, key: string, [k: string]: any }>} */
|
|
128
54
|
const entities = []
|
|
129
|
-
/** @type {Array<{ key: string, metadata: object }>} */
|
|
130
55
|
const metadata = []
|
|
131
56
|
let rewritten = ''
|
|
132
57
|
let last = 0
|
|
133
58
|
let citationCount = 0
|
|
134
59
|
let hyperlinkCount = 0
|
|
135
60
|
let latexCount = 0
|
|
136
|
-
|
|
137
|
-
/** Bracket stack — supports nested brackets inside the link text. */
|
|
138
61
|
const stack = []
|
|
139
62
|
|
|
140
63
|
for (let i = 0; i < text.length; i++) {
|
|
141
64
|
const ch = text[i]
|
|
142
65
|
|
|
143
|
-
|
|
144
|
-
if (ch === '[' && text[i - 1] !== '\\') {
|
|
145
|
-
stack.push(i)
|
|
146
|
-
continue
|
|
147
|
-
}
|
|
66
|
+
if (ch === '[' && text[i - 1] !== '\\') { stack.push(i); continue }
|
|
148
67
|
|
|
149
|
-
// `]` only triggers when followed by `(` (link) or `<` (latex).
|
|
150
68
|
if (ch === ']' && (text[i + 1] === '(' || text[i + 1] === '<')) {
|
|
151
69
|
const start = stack.pop()
|
|
152
70
|
if (start == null) continue
|
|
153
71
|
|
|
154
72
|
const open = text[i + 1]
|
|
155
73
|
const close = open === '(' ? ')' : '>'
|
|
156
|
-
const type =
|
|
74
|
+
const type = open === '(' ? 'hyperlink-or-citation' : 'latex'
|
|
157
75
|
|
|
158
|
-
// Walk to the matching close, respecting backslash escapes & nesting.
|
|
159
76
|
let end = i + 2
|
|
160
77
|
let depth = 1
|
|
161
78
|
while (end < text.length && depth) {
|
|
@@ -163,61 +80,35 @@ export function extractInlineEntities(text, opts = {}) {
|
|
|
163
80
|
else if (text[end] === close && text[end - 1] !== '\\') depth--
|
|
164
81
|
end++
|
|
165
82
|
}
|
|
166
|
-
if (depth) continue
|
|
83
|
+
if (depth) continue
|
|
167
84
|
|
|
168
85
|
const raw = text.slice(start + 1, i).trim()
|
|
169
86
|
const url = text.slice(i + 2, end - 1).trim()
|
|
170
|
-
|
|
171
|
-
/** @type {{ type: EntityType, key: string, [k: string]: any } | null} */
|
|
172
87
|
let entry = null
|
|
173
88
|
|
|
174
89
|
if (type === 'latex') {
|
|
175
90
|
if (!latex) continue
|
|
176
|
-
const [txt = '', width = null, height = null, fontHeight = null, padding = null] =
|
|
177
|
-
raw.split('|')
|
|
91
|
+
const [txt = '', width = null, height = null, fontHeight = null, padding = null] = raw.split('|')
|
|
178
92
|
const key = entityKey('LATEX', latexCount++)
|
|
179
|
-
entry = {
|
|
180
|
-
type: 'latex',
|
|
181
|
-
key,
|
|
182
|
-
text: txt,
|
|
183
|
-
url,
|
|
184
|
-
width,
|
|
185
|
-
height,
|
|
186
|
-
fontHeight,
|
|
187
|
-
padding,
|
|
188
|
-
}
|
|
93
|
+
entry = { type: 'latex', key, text: txt, url, width, height, fontHeight, padding }
|
|
189
94
|
rewritten += text.slice(last, start) + `{{${key}}}${txt || 'image'}{{/${key}}}`
|
|
190
95
|
} else if (raw) {
|
|
191
96
|
if (!hyperlink) continue
|
|
192
|
-
// Untrusted link → URL prefixed with `!`.
|
|
193
97
|
const trusted = !url.startsWith('!')
|
|
194
98
|
const cleanUrl = trusted ? url : url.slice(1)
|
|
195
99
|
const key = entityKey('HYPERLINK', hyperlinkCount++)
|
|
196
|
-
entry = {
|
|
197
|
-
type: 'hyperlink',
|
|
198
|
-
key,
|
|
199
|
-
text: raw,
|
|
200
|
-
url: cleanUrl,
|
|
201
|
-
isTrusted: trusted,
|
|
202
|
-
}
|
|
100
|
+
entry = { type: 'hyperlink', key, text: raw, url: cleanUrl, isTrusted: trusted }
|
|
203
101
|
rewritten += text.slice(last, start) + `{{${key}}}${cleanUrl}{{/${key}}}`
|
|
204
102
|
} else {
|
|
205
103
|
if (!citation) continue
|
|
206
104
|
const key = entityKey('CITATION', citationCount)
|
|
207
105
|
citationCount++
|
|
208
|
-
entry = {
|
|
209
|
-
type: 'citation',
|
|
210
|
-
key,
|
|
211
|
-
referenceId: citationCount, // 1-based, matches key index (zero-based +1)
|
|
212
|
-
text: '',
|
|
213
|
-
url,
|
|
214
|
-
}
|
|
106
|
+
entry = { type: 'citation', key, referenceId: citationCount, text: '', url }
|
|
215
107
|
rewritten += text.slice(last, start) + `{{${key}}}${url}{{/${key}}}`
|
|
216
108
|
}
|
|
217
109
|
|
|
218
110
|
last = end
|
|
219
111
|
i = end - 1
|
|
220
|
-
|
|
221
112
|
entities.push(entry)
|
|
222
113
|
const shaped = SHAPERS[entry.type](entry)
|
|
223
114
|
if (shaped) metadata.push(shaped)
|
|
@@ -1,43 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file table-metadata.js
|
|
3
|
-
* @module lumina/parsers/table-metadata
|
|
4
|
-
*
|
|
5
|
-
* Convert a 2-D array of strings into the metadata shape WhatsApp expects
|
|
6
|
-
* inside a `richResponseMessage.submessages[].tableMetadata`.
|
|
7
|
-
*
|
|
8
|
-
* The legacy `AIRich.toTableMetadata` always returned `title: ''` with no
|
|
9
|
-
* way to set it. Lumina accepts a `title` option.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
import { extractInlineEntities } from './inline-entity.js'
|
|
13
2
|
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {object} TableMetadata
|
|
16
|
-
* @property {string} title
|
|
17
|
-
* @property {Array<{ items: string[], isHeading?: boolean }>} rows Submessage metadata.
|
|
18
|
-
* @property {Array<{ is_header: boolean, cells: string[], markdown_cells?: Array<{ text: string, inline_entities?: object[] }> }>} unifiedRows Section primitive rows.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {string[][]} table 2-D array of strings. First row is the header.
|
|
23
|
-
* @param {object} [opts]
|
|
24
|
-
* @param {string} [opts.title='']
|
|
25
|
-
* @param {boolean} [opts.hyperlink=true]
|
|
26
|
-
* @param {boolean} [opts.citation=true]
|
|
27
|
-
* @param {boolean} [opts.latex=true]
|
|
28
|
-
* @returns {TableMetadata}
|
|
29
|
-
*/
|
|
30
3
|
export function toTableMetadata(table, opts = {}) {
|
|
31
4
|
if (!Array.isArray(table) || !table.every((row) => Array.isArray(row) && row.every((c) => typeof c === 'string'))) {
|
|
32
5
|
throw new TypeError('Table must be a 2-D array of strings')
|
|
33
6
|
}
|
|
34
7
|
|
|
35
8
|
const { title = '', hyperlink = true, citation = true, latex = true } = opts
|
|
36
|
-
|
|
37
9
|
const [header, ...rows] = table
|
|
38
10
|
const maxLen = Math.max(header.length, ...rows.map((r) => r.length))
|
|
39
|
-
|
|
40
|
-
/** Pad a row to the table's max column count. */
|
|
41
11
|
const normalize = (r) => [...r, ...Array(Math.max(0, maxLen - r.length)).fill('')]
|
|
42
12
|
|
|
43
13
|
const unifiedRows = [
|
|
@@ -50,12 +20,8 @@ export function toTableMetadata(table, opts = {}) {
|
|
|
50
20
|
if (extracted.metadata.length) out.inline_entities = extracted.metadata
|
|
51
21
|
return out
|
|
52
22
|
})
|
|
53
|
-
|
|
54
23
|
const hasInline = markdownCells.some((c) => c.inline_entities?.length)
|
|
55
|
-
return {
|
|
56
|
-
...row,
|
|
57
|
-
...(hasInline ? { markdown_cells: markdownCells } : {}),
|
|
58
|
-
}
|
|
24
|
+
return { ...row, ...(hasInline ? { markdown_cells: markdownCells } : {}) }
|
|
59
25
|
})
|
|
60
26
|
|
|
61
27
|
const rowsMeta = unifiedRows.map((r) => ({
|
package/src/proto/enums.js
CHANGED
|
@@ -1,93 +1,41 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file enums.js
|
|
3
|
-
* @module lumina/proto/enums
|
|
4
|
-
*
|
|
5
|
-
* Single source of truth for every magic number / magic string the WhatsApp
|
|
6
|
-
* protocol expects inside interactive messages, rich responses, and relay
|
|
7
|
-
* nodes. Update the wire format here once, and the whole framework follows.
|
|
8
|
-
*
|
|
9
|
-
* Replaces the 20+ scattered magic values documented in Tahap-1 analysis:
|
|
10
|
-
* - messageType: 1, 2, 4, 5, 9
|
|
11
|
-
* - forwardOrigin: 4
|
|
12
|
-
* - headerType: 0,1,3,4,5,6
|
|
13
|
-
* - native_flow v: '9'
|
|
14
|
-
* - botJid: '0@bot'
|
|
15
|
-
* - 12x __typename string literals
|
|
16
|
-
* - highlightType: 0-5
|
|
17
|
-
* - imagine_type: 'IMAGE' | 'ANIMATE'
|
|
18
|
-
* - etc.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Rich-response submessage type. Stored in `richResponseMessage.submessages[].messageType`.
|
|
23
|
-
*/
|
|
24
1
|
export const MessageType = Object.freeze({
|
|
25
|
-
/** Grid-image rich response. */
|
|
26
2
|
RICH_RESPONSE: 1,
|
|
27
|
-
/** Plain text submessage (also used as fallback for video/product/post). */
|
|
28
3
|
TEXT: 2,
|
|
29
|
-
/** Table metadata submessage. */
|
|
30
4
|
TABLE: 4,
|
|
31
|
-
/** Code-block metadata submessage. */
|
|
32
5
|
CODE: 5,
|
|
33
|
-
/** Reels / contentItems metadata submessage. */
|
|
34
6
|
REELS: 9,
|
|
35
7
|
})
|
|
36
8
|
|
|
37
|
-
/**
|
|
38
|
-
* `contextInfo.forwardOrigin` enum used by WhatsApp for AI bot messages.
|
|
39
|
-
*/
|
|
40
9
|
export const ForwardOrigin = Object.freeze({
|
|
41
10
|
BOT: 4,
|
|
42
11
|
})
|
|
43
12
|
|
|
44
|
-
/**
|
|
45
|
-
* ButtonsMessage headerType enum. The legacy ButtonV2 fallback used 6
|
|
46
|
-
* (LOCATION_THUMBNAIL) without documenting it — now explicit.
|
|
47
|
-
*/
|
|
48
13
|
export const HeaderType = Object.freeze({
|
|
49
14
|
EMPTY: 0,
|
|
50
15
|
TEXT: 1,
|
|
51
16
|
IMAGE: 3,
|
|
52
17
|
VIDEO: 4,
|
|
53
18
|
DOCUMENT: 5,
|
|
54
|
-
/** Pre-existing hack: location message with a JPEG thumbnail. */
|
|
55
19
|
LOCATION_THUMBNAIL: 6,
|
|
56
20
|
})
|
|
57
21
|
|
|
58
|
-
/**
|
|
59
|
-
* Native-flow biz/interactive node versions.
|
|
60
|
-
*/
|
|
61
22
|
export const NativeFlow = Object.freeze({
|
|
62
|
-
/** Outer `<interactive v="1">` version. */
|
|
63
23
|
OUTER_VERSION: '1',
|
|
64
|
-
/** Inner `<native_flow v="9">` version. */
|
|
65
24
|
FLOW_VERSION: '9',
|
|
66
|
-
/** Default flow name when mixing button types. */
|
|
67
25
|
FLOW_NAME_MIXED: 'mixed',
|
|
68
26
|
TYPE: 'native_flow',
|
|
69
27
|
BIZ_TAG: 'biz',
|
|
70
28
|
INTERACTIVE_TAG: 'interactive',
|
|
71
29
|
})
|
|
72
30
|
|
|
73
|
-
/**
|
|
74
|
-
* Hardcoded bot JID that WhatsApp expects inside `forwardedAiBotMessageInfo`.
|
|
75
|
-
*/
|
|
76
31
|
export const BOT_JID = '0@bot'
|
|
77
32
|
|
|
78
|
-
/**
|
|
79
|
-
* Layout view-model kinds. Each becomes `GenAI{Kind}LayoutViewModel`.
|
|
80
|
-
*/
|
|
81
33
|
export const LayoutKind = Object.freeze({
|
|
82
34
|
SINGLE: 'Single',
|
|
83
35
|
HSCROLL: 'HScroll',
|
|
84
36
|
ACTION_ROW: 'ActionRow',
|
|
85
37
|
})
|
|
86
38
|
|
|
87
|
-
/**
|
|
88
|
-
* Code-tokenizer highlight type numbers → string labels. Matches the
|
|
89
|
-
* `TYPE_MAP` constant in the legacy AIRich.tokenizer.
|
|
90
|
-
*/
|
|
91
39
|
export const HighlightType = Object.freeze({
|
|
92
40
|
DEFAULT: 0,
|
|
93
41
|
KEYWORD: 1,
|
|
@@ -97,7 +45,6 @@ export const HighlightType = Object.freeze({
|
|
|
97
45
|
COMMENT: 5,
|
|
98
46
|
})
|
|
99
47
|
|
|
100
|
-
/** String labels corresponding to {@link HighlightType}. */
|
|
101
48
|
export const HighlightLabel = Object.freeze({
|
|
102
49
|
0: 'DEFAULT',
|
|
103
50
|
1: 'KEYWORD',
|
|
@@ -107,34 +54,23 @@ export const HighlightLabel = Object.freeze({
|
|
|
107
54
|
5: 'COMMENT',
|
|
108
55
|
})
|
|
109
56
|
|
|
110
|
-
/** `imagine_type` field on GenAIImaginePrimitive. */
|
|
111
57
|
export const ImagineType = Object.freeze({
|
|
112
58
|
IMAGE: 'IMAGE',
|
|
113
59
|
ANIMATE: 'ANIMATE',
|
|
114
60
|
})
|
|
115
61
|
|
|
116
|
-
/** `source_type` field on GenAISearchResultPrimitive. */
|
|
117
62
|
export const SourceType = Object.freeze({
|
|
118
63
|
THIRD_PARTY: 'THIRD_PARTY',
|
|
119
64
|
})
|
|
120
65
|
|
|
121
|
-
/** `prompt_type` field on GenAIFollowUpSuggestionPillPrimitive. */
|
|
122
66
|
export const PromptType = Object.freeze({
|
|
123
67
|
SUGGESTED_PROMPT: 'SUGGESTED_PROMPT',
|
|
124
68
|
})
|
|
125
69
|
|
|
126
|
-
/** `sessionTransparencyType` field on botMetadata. */
|
|
127
70
|
export const SessionTransparencyType = Object.freeze({
|
|
128
71
|
DEFAULT: 1,
|
|
129
72
|
})
|
|
130
73
|
|
|
131
|
-
/**
|
|
132
|
-
* All GenAI `__typename` strings. Single source of truth — typo-proof.
|
|
133
|
-
*
|
|
134
|
-
* NOTE: legacy code had `GenATableUXPrimitive` (missing the second 'I').
|
|
135
|
-
* Verified against current WhatsApp Web wire format → the correct spelling
|
|
136
|
-
* is `GenAITableUXPrimitive`. Lumina corrects this.
|
|
137
|
-
*/
|
|
138
74
|
export const TYPENAME = Object.freeze({
|
|
139
75
|
MARKDOWN_TEXT: 'GenAIMarkdownTextUXPrimitive',
|
|
140
76
|
CODE: 'GenAICodeUXPrimitive',
|
|
@@ -147,12 +83,19 @@ export const TYPENAME = Object.freeze({
|
|
|
147
83
|
METADATA_TEXT: 'GenAIMetadataTextPrimitive',
|
|
148
84
|
FOLLOW_UP_PILL: 'GenAIFollowUpSuggestionPillPrimitive',
|
|
149
85
|
UNIFIED_SECTION: 'GenAIUnifiedResponseSection',
|
|
150
|
-
/** Layout view-models are suffixed `GenAI{Kind}LayoutViewModel`. */
|
|
151
86
|
layout(kind) {
|
|
152
87
|
return `GenAI${kind}LayoutViewModel`
|
|
153
88
|
},
|
|
154
89
|
})
|
|
155
90
|
|
|
91
|
+
export const SimpleButtonType = Object.freeze({
|
|
92
|
+
reply: 'quick_reply',
|
|
93
|
+
call: 'cta_call',
|
|
94
|
+
reminder: 'cta_reminder',
|
|
95
|
+
cancelReminder: 'cta_cancel_reminder',
|
|
96
|
+
address: 'address_message',
|
|
97
|
+
})
|
|
98
|
+
|
|
156
99
|
export default {
|
|
157
100
|
MessageType,
|
|
158
101
|
ForwardOrigin,
|
|
@@ -167,4 +110,5 @@ export default {
|
|
|
167
110
|
PromptType,
|
|
168
111
|
SessionTransparencyType,
|
|
169
112
|
TYPENAME,
|
|
113
|
+
SimpleButtonType,
|
|
170
114
|
}
|
package/src/proto/layouts.js
CHANGED
|
@@ -1,78 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file layouts.js
|
|
3
|
-
* @module lumina/proto/layouts
|
|
4
|
-
*
|
|
5
|
-
* Layout view-model factories. Each layout wraps one or more UX primitives
|
|
6
|
-
* inside `view_model`, with the proper `GenAI{Kind}LayoutViewModel` typename.
|
|
7
|
-
*
|
|
8
|
-
* Replaces the implicit `Array.isArray(data) ? 'primitives' : 'primitive'`
|
|
9
|
-
* branching in the legacy `AIRich.newLayout` static method — explicit
|
|
10
|
-
* single/hscroll/action-row variants instead.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
import { LayoutKind, TYPENAME } from './enums.js'
|
|
14
2
|
|
|
15
|
-
/**
|
|
16
|
-
* Wrap a single primitive in a "Single" layout view-model.
|
|
17
|
-
*
|
|
18
|
-
* @param {object} primitive
|
|
19
|
-
* @param {object} [extra] Additional fields merged at top-level.
|
|
20
|
-
* @returns {object}
|
|
21
|
-
*/
|
|
22
3
|
export function singleLayout(primitive, extra = {}) {
|
|
23
|
-
return {
|
|
24
|
-
...extra,
|
|
25
|
-
view_model: {
|
|
26
|
-
primitive,
|
|
27
|
-
__typename: TYPENAME.layout(LayoutKind.SINGLE),
|
|
28
|
-
},
|
|
29
|
-
}
|
|
4
|
+
return { ...extra, view_model: { primitive, __typename: TYPENAME.layout(LayoutKind.SINGLE) } }
|
|
30
5
|
}
|
|
31
6
|
|
|
32
|
-
/**
|
|
33
|
-
* Wrap multiple primitives in an "HScroll" (horizontal scroll) layout.
|
|
34
|
-
*
|
|
35
|
-
* @param {object[]} primitives
|
|
36
|
-
* @param {object} [extra]
|
|
37
|
-
* @returns {object}
|
|
38
|
-
*/
|
|
39
7
|
export function hscrollLayout(primitives, extra = {}) {
|
|
40
|
-
return {
|
|
41
|
-
...extra,
|
|
42
|
-
view_model: {
|
|
43
|
-
primitives,
|
|
44
|
-
__typename: TYPENAME.layout(LayoutKind.HSCROLL),
|
|
45
|
-
},
|
|
46
|
-
}
|
|
8
|
+
return { ...extra, view_model: { primitives, __typename: TYPENAME.layout(LayoutKind.HSCROLL) } }
|
|
47
9
|
}
|
|
48
10
|
|
|
49
|
-
/**
|
|
50
|
-
* Wrap multiple primitives in an "ActionRow" layout (used for follow-up
|
|
51
|
-
* suggestion pills that should appear as a horizontal row of buttons).
|
|
52
|
-
*
|
|
53
|
-
* @param {object[]} primitives
|
|
54
|
-
* @param {object} [extra]
|
|
55
|
-
* @returns {object}
|
|
56
|
-
*/
|
|
57
11
|
export function actionRowLayout(primitives, extra = {}) {
|
|
58
|
-
return {
|
|
59
|
-
...extra,
|
|
60
|
-
view_model: {
|
|
61
|
-
primitives,
|
|
62
|
-
__typename: TYPENAME.layout(LayoutKind.ACTION_ROW),
|
|
63
|
-
},
|
|
64
|
-
}
|
|
12
|
+
return { ...extra, view_model: { primitives, __typename: TYPENAME.layout(LayoutKind.ACTION_ROW) } }
|
|
65
13
|
}
|
|
66
14
|
|
|
67
|
-
/**
|
|
68
|
-
* Auto-pick helper. Picks `singleLayout` for a single primitive and
|
|
69
|
-
* `hscrollLayout` for arrays — useful for API surfaces that accept both.
|
|
70
|
-
*
|
|
71
|
-
* @param {LayoutKind} kind Forced kind; overrides auto-detection.
|
|
72
|
-
* @param {object|object[]} data
|
|
73
|
-
* @param {object} [extra]
|
|
74
|
-
* @returns {object}
|
|
75
|
-
*/
|
|
76
15
|
export function layoutFor(kind, data, extra = {}) {
|
|
77
16
|
switch (kind) {
|
|
78
17
|
case LayoutKind.SINGLE:
|
package/src/proto/primitives.js
CHANGED
|
@@ -1,85 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
* @file primitives.js
|
|
3
|
-
* @module lumina/proto/primitives
|
|
4
|
-
*
|
|
5
|
-
* Pure factory functions for every GenAI "UX primitive" used by the AI Rich
|
|
6
|
-
* Response envelope. Each factory returns a plain object ready to be wrapped
|
|
7
|
-
* by a layout view-model (see {@link module:proto/layouts}).
|
|
8
|
-
*
|
|
9
|
-
* All `__typename` strings are sourced from {@link module:proto/enums.TYPENAME}
|
|
10
|
-
* — never hardcode them here.
|
|
11
|
-
*/
|
|
1
|
+
import { TYPENAME, SourceType, PromptType } from './enums.js'
|
|
12
2
|
|
|
13
|
-
import { TYPENAME, ImagineType, SourceType, PromptType } from './enums.js'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Markdown-text primitive. May carry inline entities extracted by
|
|
17
|
-
* `extractInlineEntities`.
|
|
18
|
-
*
|
|
19
|
-
* @param {string} text
|
|
20
|
-
* @param {Array<object>} [inlineEntities]
|
|
21
|
-
*/
|
|
22
3
|
export function markdownTextPrimitive(text, inlineEntities) {
|
|
23
4
|
const primitive = { text, __typename: TYPENAME.MARKDOWN_TEXT }
|
|
24
5
|
if (inlineEntities?.length) primitive.inline_entities = inlineEntities
|
|
25
6
|
return primitive
|
|
26
7
|
}
|
|
27
8
|
|
|
28
|
-
/**
|
|
29
|
-
* Code primitive.
|
|
30
|
-
*
|
|
31
|
-
* @param {string} language
|
|
32
|
-
* @param {Array<{ content: string, type: string }>} unifiedBlocks
|
|
33
|
-
*/
|
|
34
9
|
export function codePrimitive(language, unifiedBlocks) {
|
|
35
|
-
return {
|
|
36
|
-
language,
|
|
37
|
-
code_blocks: unifiedBlocks,
|
|
38
|
-
__typename: TYPENAME.CODE,
|
|
39
|
-
}
|
|
10
|
+
return { language, code_blocks: unifiedBlocks, __typename: TYPENAME.CODE }
|
|
40
11
|
}
|
|
41
12
|
|
|
42
|
-
/**
|
|
43
|
-
* Table primitive.
|
|
44
|
-
*
|
|
45
|
-
* @param {Array<object>} unifiedRows Output of `toTableMetadata().unifiedRows`.
|
|
46
|
-
*/
|
|
47
13
|
export function tablePrimitive(unifiedRows) {
|
|
48
|
-
return {
|
|
49
|
-
rows: unifiedRows,
|
|
50
|
-
__typename: TYPENAME.TABLE,
|
|
51
|
-
}
|
|
14
|
+
return { rows: unifiedRows, __typename: TYPENAME.TABLE }
|
|
52
15
|
}
|
|
53
16
|
|
|
54
|
-
/**
|
|
55
|
-
* Search-result (sources) primitive.
|
|
56
|
-
*
|
|
57
|
-
* @param {Array<object>} sources Pre-shaped source objects.
|
|
58
|
-
*/
|
|
59
17
|
export function searchResultPrimitive(sources) {
|
|
60
|
-
return {
|
|
61
|
-
sources,
|
|
62
|
-
__typename: TYPENAME.SEARCH_RESULT,
|
|
63
|
-
}
|
|
18
|
+
return { sources, __typename: TYPENAME.SEARCH_RESULT }
|
|
64
19
|
}
|
|
65
20
|
|
|
66
|
-
/**
|
|
67
|
-
* Reel primitive (one per reel item).
|
|
68
|
-
*
|
|
69
|
-
* @param {object} item Pre-shaped reel descriptor.
|
|
70
|
-
*/
|
|
71
21
|
export function reelPrimitive(item) {
|
|
72
22
|
return { ...item, __typename: TYPENAME.REEL }
|
|
73
23
|
}
|
|
74
24
|
|
|
75
|
-
/**
|
|
76
|
-
* Imagine primitive — used for both images and videos (animate).
|
|
77
|
-
*
|
|
78
|
-
* @param {{ url: string, mime_type: string, file_length?: number, duration?: number }} media
|
|
79
|
-
* @param {'IMAGE'|'ANIMATE'} kind
|
|
80
|
-
* @param {string} [status='READY']
|
|
81
|
-
* @param {string} [thumbnailBase64] Base64 JPEG thumbnail (video only).
|
|
82
|
-
*/
|
|
83
25
|
export function imaginePrimitive(media, kind, status = 'READY', thumbnailBase64) {
|
|
84
26
|
const primitive = {
|
|
85
27
|
media,
|
|
@@ -91,39 +33,18 @@ export function imaginePrimitive(media, kind, status = 'READY', thumbnailBase64)
|
|
|
91
33
|
return primitive
|
|
92
34
|
}
|
|
93
35
|
|
|
94
|
-
/**
|
|
95
|
-
* Product-card primitive.
|
|
96
|
-
*
|
|
97
|
-
* @param {object} item Pre-shaped product descriptor (already media-resolved).
|
|
98
|
-
*/
|
|
99
36
|
export function productCardPrimitive(item) {
|
|
100
37
|
return { ...item, __typename: TYPENAME.PRODUCT_CARD }
|
|
101
38
|
}
|
|
102
39
|
|
|
103
|
-
/**
|
|
104
|
-
* Post primitive.
|
|
105
|
-
*
|
|
106
|
-
* @param {object} p Pre-shaped post descriptor (already media-resolved).
|
|
107
|
-
* @param {boolean} isCarousel
|
|
108
|
-
*/
|
|
109
40
|
export function postPrimitive(p, isCarousel) {
|
|
110
41
|
return { ...p, is_carousel: isCarousel, __typename: TYPENAME.POST }
|
|
111
42
|
}
|
|
112
43
|
|
|
113
|
-
/**
|
|
114
|
-
* Metadata-text primitive (used for footer / tip).
|
|
115
|
-
*
|
|
116
|
-
* @param {string} text
|
|
117
|
-
*/
|
|
118
44
|
export function metadataTextPrimitive(text) {
|
|
119
45
|
return { text, __typename: TYPENAME.METADATA_TEXT }
|
|
120
46
|
}
|
|
121
47
|
|
|
122
|
-
/**
|
|
123
|
-
* Follow-up suggestion pill primitive.
|
|
124
|
-
*
|
|
125
|
-
* @param {string} text
|
|
126
|
-
*/
|
|
127
48
|
export function followUpSuggestionPillPrimitive(text) {
|
|
128
49
|
return {
|
|
129
50
|
prompt_text: text,
|
|
@@ -132,11 +53,6 @@ export function followUpSuggestionPillPrimitive(text) {
|
|
|
132
53
|
}
|
|
133
54
|
}
|
|
134
55
|
|
|
135
|
-
/**
|
|
136
|
-
* Helper: shape a source entry for `searchResultPrimitive`.
|
|
137
|
-
*
|
|
138
|
-
* @param {{ iconUrl?: string, url?: string, text?: string, subtitle?: string }} src
|
|
139
|
-
*/
|
|
140
56
|
export function shapeSourceEntry({ iconUrl = '', url = '', text = '', subtitle = 'AI' } = {}) {
|
|
141
57
|
return {
|
|
142
58
|
source_type: SourceType.THIRD_PARTY,
|
|
@@ -147,9 +63,6 @@ export function shapeSourceEntry({ iconUrl = '', url = '', text = '', subtitle =
|
|
|
147
63
|
}
|
|
148
64
|
}
|
|
149
65
|
|
|
150
|
-
/**
|
|
151
|
-
* Helper: shape a single reel item for `reelPrimitive`.
|
|
152
|
-
*/
|
|
153
66
|
export function shapeReelEntry(item) {
|
|
154
67
|
return {
|
|
155
68
|
reels_url: item.videoUrl ?? item.url ?? '',
|
package/src/proto/relay-nodes.js
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file relay-nodes.js
|
|
3
|
-
* @module lumina/proto/relay-nodes
|
|
4
|
-
*
|
|
5
|
-
* Single source of truth for the `additionalNodes` block that Baileys
|
|
6
|
-
* `relayMessage` expects when sending interactive (native-flow) messages.
|
|
7
|
-
*
|
|
8
|
-
* The legacy code duplicated this 12-line block three times (Button.send,
|
|
9
|
-
* ButtonV2.send, Carousel.send). Lumina centralises it here.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
import { NativeFlow } from './enums.js'
|
|
13
2
|
|
|
14
|
-
/**
|
|
15
|
-
* Build the standard biz/interactive/native_flow additional-nodes block.
|
|
16
|
-
*
|
|
17
|
-
* @param {object} [opts]
|
|
18
|
-
* @param {string} [opts.outerVersion=NativeFlow.OUTER_VERSION]
|
|
19
|
-
* @param {string} [opts.flowVersion=NativeFlow.FLOW_VERSION]
|
|
20
|
-
* @param {string} [opts.flowName=NativeFlow.FLOW_NAME_MIXED]
|
|
21
|
-
* @param {boolean} [opts.includeBiz=true] Whether to wrap in the `<biz>` node.
|
|
22
|
-
* @returns {Array<object>} Array suitable for `relayMessage`'s `additionalNodes`.
|
|
23
|
-
*/
|
|
24
3
|
export function createInteractiveNodes(opts = {}) {
|
|
25
4
|
const outerVersion = opts.outerVersion ?? NativeFlow.OUTER_VERSION
|
|
26
5
|
const flowVersion = opts.flowVersion ?? NativeFlow.FLOW_VERSION
|
|
@@ -35,19 +14,9 @@ export function createInteractiveNodes(opts = {}) {
|
|
|
35
14
|
|
|
36
15
|
if (!includeBiz) return [interactive]
|
|
37
16
|
|
|
38
|
-
return [
|
|
39
|
-
{
|
|
40
|
-
tag: NativeFlow.BIZ_TAG,
|
|
41
|
-
attrs: {},
|
|
42
|
-
content: [interactive],
|
|
43
|
-
},
|
|
44
|
-
]
|
|
17
|
+
return [{ tag: NativeFlow.BIZ_TAG, attrs: {}, content: [interactive] }]
|
|
45
18
|
}
|
|
46
19
|
|
|
47
|
-
/**
|
|
48
|
-
* Convenience: returns the same block but with `name: 'native_flow'` only
|
|
49
|
-
* (no biz wrapper). Useful for non-business accounts.
|
|
50
|
-
*/
|
|
51
20
|
export function createBareInteractiveNodes(opts = {}) {
|
|
52
21
|
return createInteractiveNodes({ ...opts, includeBiz: false })
|
|
53
22
|
}
|