@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.
@@ -106,7 +106,7 @@ export declare namespace comms {
106
106
  * No broadcast record is created. The send is logged to the contact's
107
107
  * communication history with sourceType: 'transactional'.
108
108
  *
109
- * POST /admin/collection/:collectionId/comm/send
109
+ * POST /admin/collection/:collectionId/comm.send
110
110
  *
111
111
  * @example
112
112
  * ```typescript
package/dist/api/comms.js CHANGED
@@ -201,7 +201,7 @@ export var comms;
201
201
  * No broadcast record is created. The send is logged to the contact's
202
202
  * communication history with sourceType: 'transactional'.
203
203
  *
204
- * POST /admin/collection/:collectionId/comm/send
204
+ * POST /admin/collection/:collectionId/comm.send
205
205
  *
206
206
  * @example
207
207
  * ```typescript
@@ -222,7 +222,7 @@ export var comms;
222
222
  * ```
223
223
  */
224
224
  async function sendTransactional(collectionId, body) {
225
- const path = `/admin/collection/${encodeURIComponent(collectionId)}/comm/send`;
225
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/comm.send`;
226
226
  return post(path, body);
227
227
  }
228
228
  comms.sendTransactional = sendTransactional;
@@ -66,4 +66,42 @@ export declare namespace proof {
66
66
  * GET /admin/collection/:collectionId/product/:productId/batch/:batchId/proof
67
67
  */
68
68
  function getByBatch(collectionId: string, productId: string, batchId: string): Promise<ProofResponse[]>;
69
+ /**
70
+ * Migrate a proof to a different product within the same collection (admin only).
71
+ *
72
+ * Because the Firestore ledger document ID is `{productId}-{proofId}`, a proof
73
+ * cannot simply be re-assigned to another product by updating a field — the
74
+ * document must be re-keyed. This endpoint handles that atomically:
75
+ *
76
+ * 1. Reads the source ledger document (`{sourceProductId}-{proofId}`).
77
+ * 2. Writes a new document (`{targetProductId}-{proofId}`) with `productId`
78
+ * and `proofGroup` updated. The short `proofId` (nanoid) is unchanged.
79
+ * 3. Writes a migration history entry to the new document's `history`
80
+ * subcollection (snapshot of the original proof + migration metadata).
81
+ * 4. Copies all subcollections — `assets`, `attestations`, `history` — from
82
+ * the old document to the new one.
83
+ * 5. Deletes the old subcollections and then the old document.
84
+ *
85
+ * Repeated migrations are safe — each one appends a history record; no
86
+ * migration metadata is stored on the proof document itself.
87
+ *
88
+ * @param collectionId - Identifier of the parent collection
89
+ * @param productId - Current (source) product ID that owns the proof
90
+ * @param proofId - Identifier of the proof to migrate
91
+ * @param data - `{ targetProductId }` — the destination product
92
+ * @returns The migrated proof object (now owned by `targetProductId`)
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const migrated = await proof.migrate('coll_123', 'prod_old', 'proof_abc', {
97
+ * targetProductId: 'prod_new',
98
+ * })
99
+ * console.log(migrated.productId) // 'prod_new'
100
+ * ```
101
+ */
102
+ function migrate(collectionId: string, productId: string, proofId: string,
103
+ /** The destination product ID */
104
+ data: {
105
+ targetProductId: string;
106
+ }): Promise<ProofResponse>;
69
107
  }
package/dist/api/proof.js CHANGED
@@ -116,4 +116,44 @@ export var proof;
116
116
  return request(path);
117
117
  }
118
118
  proof.getByBatch = getByBatch;
119
+ /**
120
+ * Migrate a proof to a different product within the same collection (admin only).
121
+ *
122
+ * Because the Firestore ledger document ID is `{productId}-{proofId}`, a proof
123
+ * cannot simply be re-assigned to another product by updating a field — the
124
+ * document must be re-keyed. This endpoint handles that atomically:
125
+ *
126
+ * 1. Reads the source ledger document (`{sourceProductId}-{proofId}`).
127
+ * 2. Writes a new document (`{targetProductId}-{proofId}`) with `productId`
128
+ * and `proofGroup` updated. The short `proofId` (nanoid) is unchanged.
129
+ * 3. Writes a migration history entry to the new document's `history`
130
+ * subcollection (snapshot of the original proof + migration metadata).
131
+ * 4. Copies all subcollections — `assets`, `attestations`, `history` — from
132
+ * the old document to the new one.
133
+ * 5. Deletes the old subcollections and then the old document.
134
+ *
135
+ * Repeated migrations are safe — each one appends a history record; no
136
+ * migration metadata is stored on the proof document itself.
137
+ *
138
+ * @param collectionId - Identifier of the parent collection
139
+ * @param productId - Current (source) product ID that owns the proof
140
+ * @param proofId - Identifier of the proof to migrate
141
+ * @param data - `{ targetProductId }` — the destination product
142
+ * @returns The migrated proof object (now owned by `targetProductId`)
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const migrated = await proof.migrate('coll_123', 'prod_old', 'proof_abc', {
147
+ * targetProductId: 'prod_new',
148
+ * })
149
+ * console.log(migrated.productId) // 'prod_new'
150
+ * ```
151
+ */
152
+ async function migrate(collectionId, productId, proofId,
153
+ /** The destination product ID */
154
+ data) {
155
+ const path = `/admin/collection/${encodeURIComponent(collectionId)}/products/${encodeURIComponent(productId)}/proofs/${encodeURIComponent(proofId)}/migrate`;
156
+ return post(path, data);
157
+ }
158
+ proof.migrate = migrate;
119
159
  })(proof || (proof = {}));
@@ -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/dist/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
@@ -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'