@proveanything/smartlinks 1.7.2 → 1.7.4

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/dist/openapi.yaml CHANGED
@@ -2138,6 +2138,39 @@ paths:
2138
2138
  description: Unauthorized
2139
2139
  404:
2140
2140
  description: Not found
2141
+ /admin/collection/{collectionId}/comm.send:
2142
+ post:
2143
+ tags:
2144
+ - comms
2145
+ summary: Send a single transactional message to one contact using a template.
2146
+ operationId: comms_sendTransactional
2147
+ security:
2148
+ - bearerAuth: []
2149
+ parameters:
2150
+ - name: collectionId
2151
+ in: path
2152
+ required: true
2153
+ schema:
2154
+ type: string
2155
+ responses:
2156
+ 200:
2157
+ description: Success
2158
+ content:
2159
+ application/json:
2160
+ schema:
2161
+ $ref: "#/components/schemas/TransactionalSendResult"
2162
+ 400:
2163
+ description: Bad request
2164
+ 401:
2165
+ description: Unauthorized
2166
+ 404:
2167
+ description: Not found
2168
+ requestBody:
2169
+ required: true
2170
+ content:
2171
+ application/json:
2172
+ schema:
2173
+ $ref: "#/components/schemas/TransactionalSendRequest"
2141
2174
  /admin/collection/{collectionId}/comm.settings:
2142
2175
  patch:
2143
2176
  tags:
@@ -2421,39 +2454,6 @@ paths:
2421
2454
  application/json:
2422
2455
  schema:
2423
2456
  $ref: "#/components/schemas/CommsRecipientsWithoutActionQuery"
2424
- /admin/collection/{collectionId}/comm/send:
2425
- post:
2426
- tags:
2427
- - comms
2428
- summary: Send a single transactional message to one contact using a template.
2429
- operationId: comms_sendTransactional
2430
- security:
2431
- - bearerAuth: []
2432
- parameters:
2433
- - name: collectionId
2434
- in: path
2435
- required: true
2436
- schema:
2437
- type: string
2438
- responses:
2439
- 200:
2440
- description: Success
2441
- content:
2442
- application/json:
2443
- schema:
2444
- $ref: "#/components/schemas/TransactionalSendResult"
2445
- 400:
2446
- description: Bad request
2447
- 401:
2448
- description: Unauthorized
2449
- 404:
2450
- description: Not found
2451
- requestBody:
2452
- required: true
2453
- content:
2454
- application/json:
2455
- schema:
2456
- $ref: "#/components/schemas/TransactionalSendRequest"
2457
2457
  /admin/collection/{collectionId}/contacts:
2458
2458
  get:
2459
2459
  tags:
@@ -5203,6 +5203,50 @@ paths:
5203
5203
  description: Unauthorized
5204
5204
  404:
5205
5205
  description: Not found
5206
+ /admin/collection/{collectionId}/products/{productId}/proofs/{proofId}/migrate:
5207
+ post:
5208
+ tags:
5209
+ - proof
5210
+ summary: proof.migrate
5211
+ operationId: proof_migrate
5212
+ security:
5213
+ - bearerAuth: []
5214
+ parameters:
5215
+ - name: collectionId
5216
+ in: path
5217
+ required: true
5218
+ schema:
5219
+ type: string
5220
+ - name: productId
5221
+ in: path
5222
+ required: true
5223
+ schema:
5224
+ type: string
5225
+ - name: proofId
5226
+ in: path
5227
+ required: true
5228
+ schema:
5229
+ type: string
5230
+ responses:
5231
+ 200:
5232
+ description: Success
5233
+ content:
5234
+ application/json:
5235
+ schema:
5236
+ $ref: "#/components/schemas/ProofResponse"
5237
+ 400:
5238
+ description: Bad request
5239
+ 401:
5240
+ description: Unauthorized
5241
+ 404:
5242
+ description: Not found
5243
+ requestBody:
5244
+ required: true
5245
+ content:
5246
+ application/json:
5247
+ schema:
5248
+ type: object
5249
+ additionalProperties: true
5206
5250
  /admin/collection/{collectionId}/proof/findByUser/{userId}:
5207
5251
  get:
5208
5252
  tags:
@@ -280,7 +280,7 @@ export interface SubscriptionsResolveResponse {
280
280
  * No broadcast record is created; the send is logged directly to the
281
281
  * contact's communication history with sourceType: 'transactional'.
282
282
  *
283
- * POST /admin/collection/:collectionId/comm/send
283
+ * POST /admin/collection/:collectionId/comm.send
284
284
  */
285
285
  export interface TransactionalSendRequest {
286
286
  /** CRM contact UUID */
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.7.2 | Generated: 2026-03-04T07:38:45.571Z
3
+ Version: 1.7.4 | Generated: 2026-03-10T14:18:07.271Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -3006,7 +3006,7 @@ export interface SubscriptionsResolveResponse {
3006
3006
  * No broadcast record is created; the send is logged directly to the
3007
3007
  * contact's communication history with sourceType: 'transactional'.
3008
3008
  *
3009
- * POST /admin/collection/:collectionId/comm/send
3009
+ * POST /admin/collection/:collectionId/comm.send
3010
3010
  */
3011
3011
  export interface TransactionalSendRequest {
3012
3012
  /** CRM contact UUID */
@@ -5978,7 +5978,7 @@ Analytics: Recipients who performed an action, optionally with outcome. POST /ad
5978
5978
 
5979
5979
  **sendTransactional**(collectionId: string,
5980
5980
  body: TransactionalSendRequest) → `Promise<TransactionalSendResult>`
5981
- Send a single transactional message to one contact using a template. No broadcast record is created. The send is logged to the contact's communication history with sourceType: 'transactional'. POST /admin/collection/:collectionId/comm/send ```typescript const result = await comms.sendTransactional(collectionId, { contactId: 'e4f2a1b0-...', templateId: 'warranty-update', channel: 'preferred', props: { claimRef: 'CLM-0042', decision: 'approved' }, include: { productId: 'prod-abc123', appCase: 'c9d1e2f3-...' }, ref: 'warranty-decision-notification', appId: 'warrantyApp', }) if (result.ok) { console.log(`Sent via ${result.channel}`, result.messageId) } else { console.error('Send failed:', result.error) } ```
5981
+ Send a single transactional message to one contact using a template. No broadcast record is created. The send is logged to the contact's communication history with sourceType: 'transactional'. POST /admin/collection/:collectionId/comm.send ```typescript const result = await comms.sendTransactional(collectionId, { contactId: 'e4f2a1b0-...', templateId: 'warranty-update', channel: 'preferred', props: { claimRef: 'CLM-0042', decision: 'approved' }, include: { productId: 'prod-abc123', appCase: 'c9d1e2f3-...' }, ref: 'warranty-decision-notification', appId: 'warrantyApp', }) if (result.ok) { console.log(`Sent via ${result.channel}`, result.messageId) } else { console.error('Send failed:', result.error) } ```
5982
5982
 
5983
5983
  **logCommunicationEvent**(collectionId: string,
5984
5984
  body: LogCommunicationEventBody) → `Promise<AppendResult>`
@@ -6470,6 +6470,13 @@ Find proofs for a product (admin only). POST /admin/collection/:collectionId/pro
6470
6470
  batchId: string) → `Promise<ProofResponse[]>`
6471
6471
  Get proofs for a batch (admin only). GET /admin/collection/:collectionId/product/:productId/batch/:batchId/proof
6472
6472
 
6473
+ **migrate**(collectionId: string,
6474
+ productId: string,
6475
+ proofId: string,
6476
+ /** The destination product ID */
6477
+ data: { targetProductId: string }) → `Promise<ProofResponse>`
6478
+ Migrate a proof to a different product within the same collection (admin only). Because the Firestore ledger document ID is `{productId}-{proofId}`, a proof cannot simply be re-assigned to another product by updating a field — the document must be re-keyed. This endpoint handles that atomically: 1. Reads the source ledger document (`{sourceProductId}-{proofId}`). 2. Writes a new document (`{targetProductId}-{proofId}`) with `productId` and `proofGroup` updated. The short `proofId` (nanoid) is unchanged. 3. Writes a migration history entry to the new document's `history` subcollection (snapshot of the original proof + migration metadata). 4. Copies all subcollections — `assets`, `attestations`, `history` — from the old document to the new one. 5. Deletes the old subcollections and then the old document. Repeated migrations are safe — each one appends a history record; no migration metadata is stored on the proof document itself. ```typescript const migrated = await proof.migrate('coll_123', 'prod_old', 'proof_abc', { targetProductId: 'prod_new', }) console.log(migrated.productId) // 'prod_new' ```
6479
+
6473
6480
  ### qr
6474
6481
 
6475
6482
  **lookupShortCode**(shortId: string, code: string) → `Promise<QrShortCodeLookupResponse>`
package/docs/ai.md CHANGED
@@ -19,6 +19,7 @@ Complete guide to using AI capabilities in the SmartLinks SDK, including chat co
19
19
  - [Error Handling](#error-handling)
20
20
  - [Rate Limiting](#rate-limiting)
21
21
  - [Best Practices](#best-practices)
22
+ - [Providing Content to the AI Assistant](#providing-content-to-the-ai-assistant)
22
23
 
23
24
  ---
24
25
 
@@ -1547,6 +1548,294 @@ console.log('Estimated cost: $', cost.toFixed(4));
1547
1548
 
1548
1549
  ---
1549
1550
 
1551
+ ## Providing Content to the AI Assistant
1552
+
1553
+ The portal's built-in AI assistant can discuss the content currently visible to the user. To make this work, your app must supply contextual content when the assistant requests it. There are three extraction methods, tried in priority order:
1554
+
1555
+ | Priority | Method | When Used |
1556
+ |----------|--------|-----------|
1557
+ | 1 | **Direct prop callback** | Container/widget rendered in the parent React context |
1558
+ | 2 | **PostMessage protocol** | App rendered in an iframe |
1559
+ | 3 | **DOM text fallback** | Neither of the above responded |
1560
+
1561
+ This section covers **methods 1 and 2** — the ones you implement in your app.
1562
+
1563
+ ### Method 1: Direct Prop (`onRequestAIContent`)
1564
+
1565
+ When your app is rendered as a **container** (a direct component in the parent's React tree), the framework passes an `onRequestAIContent` prop to your exported component. You do **not** call this prop yourself — the framework calls it when the AI assistant needs context.
1566
+
1567
+ Structure your component to make current state accessible when the callback fires:
1568
+
1569
+ ```tsx
1570
+ import { useEffect, useRef } from 'react';
1571
+
1572
+ export function PublicContainer(props) {
1573
+ const { onRequestAIContent, appId, ...rest } = props;
1574
+
1575
+ // Keep a ref to the latest content so the callback always returns fresh data
1576
+ const currentContentRef = useRef(null);
1577
+
1578
+ useEffect(() => {
1579
+ currentContentRef.current = buildCurrentContent();
1580
+ }, [relevantState]);
1581
+
1582
+ return <div data-app-container={appId}>{/* your UI */}</div>;
1583
+ }
1584
+ ```
1585
+
1586
+ The framework registers the content provider internally via `useAIContentExtraction.registerContentProvider()`. Your component just needs to respond when the callback is invoked.
1587
+
1588
+ ### Method 2: PostMessage Protocol (Iframes)
1589
+
1590
+ For iframe-embedded apps the framework sends a `postMessage` request and expects a response within **500 ms**. If your app doesn't reply in time the framework falls back to DOM text extraction.
1591
+
1592
+ **Request sent by the framework to your iframe:**
1593
+
1594
+ ```typescript
1595
+ {
1596
+ type: 'smartlinks:request-ai-content',
1597
+ requestId: 'ai-content-1709834567890-abc123' // unique per request
1598
+ }
1599
+ ```
1600
+
1601
+ **Response your app must send back:**
1602
+
1603
+ ```typescript
1604
+ {
1605
+ type: 'smartlinks:ai-content-response',
1606
+ requestId: 'ai-content-1709834567890-abc123', // echo back the requestId
1607
+ content: AIContentResponse
1608
+ }
1609
+ ```
1610
+
1611
+ **Minimal implementation:**
1612
+
1613
+ ```typescript
1614
+ window.addEventListener('message', async (event) => {
1615
+ if (event.data?.type === 'smartlinks:request-ai-content') {
1616
+ const content = await gatherAIContent();
1617
+
1618
+ window.parent.postMessage({
1619
+ type: 'smartlinks:ai-content-response',
1620
+ requestId: event.data.requestId,
1621
+ content,
1622
+ }, '*');
1623
+ }
1624
+ });
1625
+
1626
+ async function gatherAIContent(): Promise<AIContentResponse> {
1627
+ return {
1628
+ text: 'The user is viewing the warranty registration form...',
1629
+ contentLabel: 'Warranty Registration',
1630
+ };
1631
+ }
1632
+ ```
1633
+
1634
+ ### The `AIContentResponse` Interface
1635
+
1636
+ ```typescript
1637
+ interface AIContentResponse {
1638
+ /**
1639
+ * Plain text or markdown for the AI to use as context.
1640
+ * Injected into the system prompt. Max recommended: ~4000 characters.
1641
+ */
1642
+ text: string;
1643
+
1644
+ /**
1645
+ * Optional structured metadata (key-value pairs).
1646
+ * Not used directly in prompts but available for custom providers.
1647
+ */
1648
+ metadata?: Record<string, unknown>;
1649
+
1650
+ /**
1651
+ * Pre-built RAG configuration. When provided, the assistant can use
1652
+ * SL.ai.public.chat() to ground answers in indexed product documents.
1653
+ */
1654
+ ragHint?: {
1655
+ /** The product ID whose indexed documents should be queried */
1656
+ productId: string;
1657
+ /** Optional session ID for multi-turn RAG conversations */
1658
+ sessionId?: string;
1659
+ /** Optional context hint to help scope the RAG query */
1660
+ context?: string;
1661
+ };
1662
+
1663
+ /**
1664
+ * Human-readable label shown in context-update messages
1665
+ * (e.g. "Product Manual", "FAQ").
1666
+ */
1667
+ contentLabel?: string;
1668
+
1669
+ /**
1670
+ * How the assistant should use this content:
1671
+ * - 'context' (default): inject text into the system prompt
1672
+ * - 'rag': use ragHint to query indexed docs via SL.ai.public.chat()
1673
+ * - 'hybrid': inject text as context AND ground answers via RAG
1674
+ */
1675
+ strategy?: 'context' | 'rag' | 'hybrid';
1676
+ }
1677
+ ```
1678
+
1679
+ ### Response Strategies
1680
+
1681
+ #### `context` (Default)
1682
+
1683
+ Return readable text. The assistant injects it into the system prompt as background knowledge. Good for descriptions, summaries, structured data, and FAQs.
1684
+
1685
+ ```typescript
1686
+ return {
1687
+ text: `
1688
+ ## Wine Details
1689
+ - **Name**: 2023 Château Margaux
1690
+ - **Region**: Bordeaux, France
1691
+ - **Tasting Notes**: Dark fruit, cedar, tobacco
1692
+ - **Food Pairing**: Lamb, aged cheese
1693
+ `,
1694
+ contentLabel: 'Wine Information',
1695
+ strategy: 'context',
1696
+ };
1697
+ ```
1698
+
1699
+ #### `rag`
1700
+
1701
+ Tell the assistant to query pre-indexed documents via SmartLinks RAG. The `text` field is minimal — the real knowledge comes from the indexed docs. Good for product manuals, large document sets, and technical specs.
1702
+
1703
+ ```typescript
1704
+ return {
1705
+ text: 'The user is viewing the espresso machine product page.',
1706
+ contentLabel: 'Product Assistant',
1707
+ strategy: 'rag',
1708
+ ragHint: {
1709
+ productId: 'espresso-machine-pro',
1710
+ sessionId: `rag-session-${userId}`,
1711
+ context: 'User is on the troubleshooting section',
1712
+ },
1713
+ };
1714
+ ```
1715
+
1716
+ When the assistant receives a `rag` strategy it routes the question through `SL.ai.public.chat()`:
1717
+
1718
+ ```typescript
1719
+ const response = await SL.ai.public.chat(collectionId, {
1720
+ productId: ragHint.productId,
1721
+ userId: currentUserId,
1722
+ message: userQuestion,
1723
+ sessionId: ragHint.sessionId,
1724
+ });
1725
+ ```
1726
+
1727
+ #### `hybrid`
1728
+
1729
+ Combines both — the `text` is injected as additional context **and** the user's questions are also grounded via RAG. Good for museum exhibits, guided experiences, or any scenario with rich metadata alongside large document sets.
1730
+
1731
+ ```typescript
1732
+ return {
1733
+ text: `
1734
+ ## Exhibit: The Starry Night
1735
+ - **Artist**: Vincent van Gogh
1736
+ - **Year**: 1889
1737
+ - **Current Location**: Gallery 3, East Wing
1738
+ - **Audio Guide**: Available in 12 languages
1739
+ `,
1740
+ contentLabel: 'Museum Exhibit',
1741
+ strategy: 'hybrid',
1742
+ ragHint: {
1743
+ productId: 'starry-night-exhibit',
1744
+ context: 'Art history and technique questions',
1745
+ },
1746
+ };
1747
+ ```
1748
+
1749
+ ### Content Extraction Examples
1750
+
1751
+ #### Museum Guide App
1752
+
1753
+ ```typescript
1754
+ async function gatherAIContent(): Promise<AIContentResponse> {
1755
+ const exhibit = getCurrentExhibit();
1756
+
1757
+ return {
1758
+ text: `
1759
+ Exhibit: "${exhibit.title}" by ${exhibit.artist}
1760
+ Period: ${exhibit.period}
1761
+ Medium: ${exhibit.medium}
1762
+ Description: ${exhibit.curatorNotes}
1763
+ Related works in this gallery: ${exhibit.relatedWorks.join(', ')}
1764
+ `.trim(),
1765
+ contentLabel: `Exhibit: ${exhibit.title}`,
1766
+ strategy: 'hybrid',
1767
+ ragHint: {
1768
+ productId: exhibit.smartlinksProductId,
1769
+ context: `Art history, technique, and visitor information for ${exhibit.title}`,
1770
+ },
1771
+ metadata: {
1772
+ exhibitId: exhibit.id,
1773
+ gallery: exhibit.gallery,
1774
+ audioGuideAvailable: exhibit.hasAudioGuide,
1775
+ },
1776
+ };
1777
+ }
1778
+ ```
1779
+
1780
+ #### Wine Product App
1781
+
1782
+ ```typescript
1783
+ async function gatherAIContent(): Promise<AIContentResponse> {
1784
+ const wine = getCurrentWine();
1785
+ const reviews = await fetchRecentReviews(wine.id, 5);
1786
+
1787
+ return {
1788
+ text: `
1789
+ Wine: ${wine.name} (${wine.vintage})
1790
+ Winery: ${wine.winery}
1791
+ Region: ${wine.region}, ${wine.country}
1792
+ Grape: ${wine.grape}
1793
+ ABV: ${wine.abv}%
1794
+ Price: ${wine.price}
1795
+ Tasting Notes: ${wine.tastingNotes}
1796
+
1797
+ Recent Reviews:
1798
+ ${reviews.map(r => `- "${r.text}" (${r.rating}/5)`).join('\n')}
1799
+ `.trim(),
1800
+ contentLabel: 'Wine Details',
1801
+ strategy: 'context',
1802
+ };
1803
+ }
1804
+ ```
1805
+
1806
+ #### Equipment Manual App (RAG-only)
1807
+
1808
+ ```typescript
1809
+ async function gatherAIContent(): Promise<AIContentResponse> {
1810
+ const equipment = getCurrentEquipment();
1811
+
1812
+ return {
1813
+ text: `User is viewing: ${equipment.name} (Model: ${equipment.modelNumber})`,
1814
+ contentLabel: `${equipment.name} Assistant`,
1815
+ strategy: 'rag',
1816
+ ragHint: {
1817
+ productId: equipment.smartlinksProductId,
1818
+ sessionId: `manual-${equipment.id}-${Date.now()}`,
1819
+ context: 'Technical manual, troubleshooting, and maintenance',
1820
+ },
1821
+ };
1822
+ }
1823
+ ```
1824
+
1825
+ ### Timing & Lifecycle
1826
+
1827
+ - **On navigation**: When the user navigates to a new product/proof/app, the assistant automatically requests fresh content and injects a context-update system message.
1828
+ - **On first message**: If no content has been gathered yet, the assistant requests it before the first AI call.
1829
+ - **On manual refresh**: The assistant can re-request content at any time (e.g. if the user's view within the app has changed).
1830
+
1831
+ Content is requested **lazily** — your handler is only called when the AI assistant is active and needs context. If the user never opens the assistant, your handler is never called.
1832
+
1833
+ ### Method 3: DOM Fallback (Automatic)
1834
+
1835
+ If your app doesn't implement either of the above, the framework extracts `innerText` from the DOM element with `data-app-container="{appId}"`, truncated to ~4000 characters. This is a **last resort** — content quality is much lower than a structured response. Implementing Method 1 or 2 is strongly recommended.
1836
+
1837
+ ---
1838
+
1550
1839
  ## Related Documentation
1551
1840
 
1552
1841
  - [API Summary](./API_SUMMARY.md) - Complete API reference
package/docs/comms.md CHANGED
@@ -19,7 +19,7 @@ This guide covers the full communications surface of the SDK: transactional send
19
19
 
20
20
  Sends a single message to one contact using a template. No broadcast record is created. The send is logged to the contact's communication history with `sourceType: 'transactional'`.
21
21
 
22
- **Endpoint:** `POST /admin/collection/:collectionId/comm/send`
22
+ **Endpoint:** `POST /admin/collection/:collectionId/comm.send`
23
23
 
24
24
  ```typescript
25
25
  import { comms } from '@proveanything/smartlinks'