@matterfact/embed 0.4.0 → 0.6.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.
Files changed (54) hide show
  1. package/README.md +62 -6
  2. package/dist/{chunk-523BCS2Z.js → chunk-4Q2ROXLR.js} +2 -2
  3. package/dist/chunk-7I37ZFAJ.js +2 -0
  4. package/dist/{chunk-Z4WT3TIZ.js → chunk-AXKXNYRT.js} +39 -38
  5. package/dist/chunk-AXKXNYRT.js.map +1 -0
  6. package/dist/chunk-D7R6WVHG.js +2 -0
  7. package/dist/chunk-D7R6WVHG.js.map +7 -0
  8. package/dist/{chunk-SBNMM3ZV.js → chunk-RS6RMZ77.js} +39 -37
  9. package/dist/chunk-RS6RMZ77.js.map +1 -0
  10. package/dist/{context-432WQST4.js → context-WU2F5CBC.js} +6 -4
  11. package/dist/context-YX2KXFLI.js +3 -0
  12. package/dist/{context-KZAUOIR6.js.map → context-YX2KXFLI.js.map} +1 -1
  13. package/dist/embed.js +1 -1
  14. package/dist/embed.js.map +2 -2
  15. package/dist/index.cjs +58 -50
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +37 -3
  18. package/dist/index.d.ts +37 -3
  19. package/dist/index.js +22 -15
  20. package/dist/index.js.map +1 -1
  21. package/dist/react.cjs +64 -50
  22. package/dist/react.cjs.map +1 -1
  23. package/dist/react.d.cts +35 -1
  24. package/dist/react.d.ts +35 -1
  25. package/dist/react.js +28 -15
  26. package/dist/react.js.map +1 -1
  27. package/dist/{snapshot-SOMSG2OF.js → snapshot-JOGZWESK.js} +2 -2
  28. package/dist/{snapshot-Q3CALMHD.js → snapshot-MUXE7KXX.js} +3 -3
  29. package/dist/{snapshot-Q3CALMHD.js.map → snapshot-MUXE7KXX.js.map} +1 -1
  30. package/examples/README.md +46 -0
  31. package/examples/demo-host/index.html +163 -0
  32. package/examples/demo-host/serve.mjs +62 -0
  33. package/examples/embed-demo/.env.example +24 -0
  34. package/examples/embed-demo/README.md +72 -0
  35. package/examples/embed-demo/index.html +12 -0
  36. package/examples/embed-demo/package.json +25 -0
  37. package/examples/embed-demo/src/App.tsx +239 -0
  38. package/examples/embed-demo/src/config.ts +43 -0
  39. package/examples/embed-demo/src/main.tsx +10 -0
  40. package/examples/embed-demo/src/styles.css +354 -0
  41. package/examples/embed-demo/tsconfig.json +16 -0
  42. package/examples/embed-demo/vite.config.ts +11 -0
  43. package/examples/host-panel.html +259 -0
  44. package/package.json +3 -2
  45. package/dist/chunk-SBNMM3ZV.js.map +0 -1
  46. package/dist/chunk-URIBHOOQ.js +0 -2
  47. package/dist/chunk-XLQ36ZMV.js +0 -2
  48. package/dist/chunk-XLQ36ZMV.js.map +0 -7
  49. package/dist/chunk-Z4WT3TIZ.js.map +0 -1
  50. package/dist/context-KZAUOIR6.js +0 -3
  51. /package/dist/{chunk-523BCS2Z.js.map → chunk-4Q2ROXLR.js.map} +0 -0
  52. /package/dist/{chunk-URIBHOOQ.js.map → chunk-7I37ZFAJ.js.map} +0 -0
  53. /package/dist/{context-432WQST4.js.map → context-WU2F5CBC.js.map} +0 -0
  54. /package/dist/{snapshot-SOMSG2OF.js.map → snapshot-JOGZWESK.js.map} +0 -0
@@ -0,0 +1,72 @@
1
+ # matterfact embed demo
2
+
3
+ A tiny React + Vite app that puts **both** matterfact embed surfaces on one page using
4
+ `@matterfact/embed`:
5
+
6
+ - **`<MatterfactAgent inline>`** — the chat, mounted in the page's right rail;
7
+ - **`<MatterfactArtifact>`** — a matterfact artifact embedded in the page.
8
+
9
+ Both on the page means the agent auto-detects the artifact by origin and can answer
10
+ questions about the data behind it — no wiring.
11
+
12
+ ## Run it
13
+
14
+ ```bash
15
+ npm install
16
+ cp .env.example .env # fill in your key + artifact triple
17
+ npm run dev # → http://localhost:8000
18
+ ```
19
+
20
+ If anything's missing the app shows a setup screen naming the exact env vars still needed.
21
+
22
+ > Requires `@matterfact/embed` **>= 0.5.0** — earlier versions' loader auto-opens a stale
23
+ > sign-in popup that errors "Missing authorization parameters" on Entra deployments.
24
+
25
+ ## Configure — everything lives in `.env`
26
+
27
+ Vite exposes only `VITE_`-prefixed vars to the app.
28
+
29
+ | Variable | What |
30
+ | --------------------------------------------- | ----------------------------------------------------------------------------- |
31
+ | `VITE_MF_ENV` | Deployment: `ecm` \| `prod` \| `staging` \| `local`. Picks the widget origin. |
32
+ | `VITE_MF_WIDGET_ORIGIN` | Optional. Overrides the origin (required for `staging`). |
33
+ | `VITE_MF_PUBLISHABLE_KEY` | The agent's publishable key (`pk_live_…` / `pk_test_…`). |
34
+ | `VITE_MF_ARTIFACT_SLUG` / `_OWNER` / `_TOKEN` | The artifact — from its **Share → Embed** dialog. |
35
+ | `VITE_MF_SURFACE` | Free-form usage/history label. |
36
+ | `VITE_MF_THEME` | `light` \| `dark` \| `auto`. |
37
+
38
+ ### Env → origin
39
+
40
+ | `VITE_MF_ENV` | Widget origin |
41
+ | ------------- | ------------------------------------------------------------------------- |
42
+ | `ecm` | `https://emc.matterfact.com` |
43
+ | `prod` | `https://app.matterfact.com` |
44
+ | `staging` | _(set `VITE_MF_WIDGET_ORIGIN`)_ |
45
+ | `local` | `http://localhost:3000` _(wherever your dev server serves `/embed/chat`)_ |
46
+
47
+ ## The one gotcha: origin registration
48
+
49
+ The publishable key is **origin-pinned** (scheme + host + port, exact match). This app runs on
50
+ `http://localhost:8000` (fixed via `strictPort`). That exact origin must be registered as an
51
+ allowed origin on the key in the target deployment (e.g. minted against emc-api for `ecm`). If
52
+ it isn't, the agent iframe renders nothing — the key isn't wrong, the origin just isn't allowed.
53
+
54
+ ## Signing in
55
+
56
+ The widget runs its own sign-in in a popup — a cross-site iframe has no ambient session, so
57
+ this is the honest production behaviour. Silent host-auth (`getAuthToken`) is out of scope here.
58
+
59
+ ## Inspect what the agent sees
60
+
61
+ Add `?mfdev=1` to the URL — a panel shows exactly what the widget sends each turn.
62
+
63
+ ## The two lines that matter
64
+
65
+ ```tsx
66
+ import { MatterfactAgent, MatterfactArtifact } from '@matterfact/embed/react';
67
+
68
+ <MatterfactAgent inline publishableKey="pk_live_…" widgetOrigin="https://emc.matterfact.com" />
69
+ <MatterfactArtifact slug="…" owner="you@firm.com" token="…" widgetOrigin="https://emc.matterfact.com" />
70
+ ```
71
+
72
+ See the package [README](../../README.md) for the full API.
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Meridian Capital — matterfact embed demo</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "matterfact-embed-demo",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "A tiny React app showing the matterfact embed agent + embed artifact, configured via .env.",
6
+ "type": "module",
7
+ "license": "Apache-2.0",
8
+ "scripts": {
9
+ "dev": "vite",
10
+ "build": "tsc --noEmit && vite build",
11
+ "preview": "vite preview --port 8000 --strictPort"
12
+ },
13
+ "dependencies": {
14
+ "@matterfact/embed": "^0.6.0",
15
+ "react": "^18.3.1",
16
+ "react-dom": "^18.3.1"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^18.3.5",
20
+ "@types/react-dom": "^18.3.0",
21
+ "@vitejs/plugin-react": "^4.3.1",
22
+ "typescript": "^5.5.4",
23
+ "vite": "^5.4.0"
24
+ }
25
+ }
@@ -0,0 +1,239 @@
1
+ import { useState } from 'react';
2
+ import { MatterfactAgent, MatterfactArtifact } from '@matterfact/embed/react';
3
+ import { config, missingConfig } from './config';
4
+
5
+ const HOLDINGS = [
6
+ { t: 'NVDA', w: '9.4%', d: '+2.8%', y: '+41%', th: 'AI datacenter demand' },
7
+ { t: 'MSFT', w: '8.1%', d: '+0.9%', y: '+22%', th: 'Copilot attach' },
8
+ {
9
+ t: 'UNH',
10
+ w: '6.7%',
11
+ d: '−1.2%',
12
+ y: '−8%',
13
+ th: 'MLR pressure',
14
+ dn: true,
15
+ yn: true,
16
+ },
17
+ { t: 'V', w: '5.9%', d: '+0.4%', y: '+11%', th: 'Cross-border recovery' },
18
+ { t: 'LLY', w: '5.2%', d: '+1.7%', y: '+34%', th: 'GLP-1 ramp' },
19
+ ];
20
+
21
+ export function App() {
22
+ const [panelOpen, setPanelOpen] = useState(true);
23
+ // A bit of state that changes WITHOUT a URL navigation — the exact case pull-based
24
+ // context exists for. Selecting a row must reach the agent, but the path never changes.
25
+ const [selected, setSelected] = useState<string | null>(null);
26
+ const missing = missingConfig();
27
+
28
+ if (missing.length) return <Setup missing={missing} />;
29
+
30
+ // PULL-BASED PAGE CONTEXT. Returns what this page is RIGHT NOW, including the holding the
31
+ // user has selected. The widget calls this at the start of every turn, so a selection —
32
+ // which changes no URL — still rides on the next question. (The React component reads this
33
+ // through a ref, so a fresh closure over `selected` each render is exactly right.)
34
+ const getPageContext = () => ({
35
+ route: '/portfolios/growth-sleeve',
36
+ description:
37
+ 'Meridian Capital — Growth Sleeve portfolio: 12 holdings, $48.2M, +18.7% YTD, ' +
38
+ '2.1% behind the S&P 500.' +
39
+ (selected ? ` The user has selected ${selected}.` : ''),
40
+ entities: HOLDINGS.map((h) => ({
41
+ kind: 'x:ticker',
42
+ id: h.t,
43
+ label: h.t,
44
+ data: { weight: h.w, ytd: h.y, thesis: h.th, selected: h.t === selected },
45
+ })),
46
+ data: { selectedHolding: selected },
47
+ });
48
+
49
+ return (
50
+ <div className="app" data-panel={panelOpen ? 'open' : 'closed'}>
51
+ <header>
52
+ <div className="brand">
53
+ <span className="mark">M</span> Meridian Capital{' '}
54
+ <small>· embed demo</small>
55
+ </div>
56
+ <nav>
57
+ <a href="#">Positions</a>
58
+ <a href="#" className="active">
59
+ Growth Sleeve
60
+ </a>
61
+ <a href="#">Research</a>
62
+ </nav>
63
+ <span className="spacer" />
64
+ <span className="env-chip">
65
+ {config.env} · {config.widgetOrigin.replace(/^https?:\/\//, '')}
66
+ </span>
67
+ <button className="toggle" onClick={() => setPanelOpen((v) => !v)}>
68
+ <span className="dot" /> Assistant
69
+ </button>
70
+ </header>
71
+
72
+ <div className="layout">
73
+ <main>
74
+ <div className="eyebrow">Portfolio</div>
75
+ <h1>Growth Sleeve</h1>
76
+ <div className="sub">
77
+ 12 holdings · rebalanced 3 days ago · benchmark S&amp;P 500
78
+ </div>
79
+
80
+ <div className="kpis">
81
+ <Kpi label="Market value" value="$48.2M" />
82
+ <Kpi label="Day change" value="+1.34%" tone="pos" />
83
+ <Kpi label="YTD" value="+18.7%" tone="pos" />
84
+ <Kpi label="vs benchmark" value="−2.1%" tone="neg" />
85
+ </div>
86
+
87
+ <div className="section-title">Embedded matterfact artifact</div>
88
+ <div className="artifact-frame">
89
+ <div className="artifact-badge">
90
+ <span className="mf">✦</span> matterfact artifact ·{' '}
91
+ <code>{config.artifact.slug}</code>
92
+ </div>
93
+ {/* The agent, mounted on the same page below, auto-detects this artifact
94
+ by origin and can answer questions about the data behind it. */}
95
+ <MatterfactArtifact
96
+ slug={config.artifact.slug}
97
+ owner={config.artifact.owner}
98
+ token={config.artifact.token}
99
+ widgetOrigin={config.widgetOrigin}
100
+ theme={config.theme}
101
+ style={{ width: '100%', height: '100%' }}
102
+ />
103
+ </div>
104
+ <p className="artifact-caption">
105
+ <strong>Artifact-aware:</strong> the assistant detects this embedded
106
+ artifact by its origin and can pull the real data behind it — try
107
+ asking it to summarise the artifact.
108
+ </p>
109
+
110
+ <div className="section-title">Holdings</div>
111
+ <table>
112
+ <thead>
113
+ <tr>
114
+ <th>Ticker</th>
115
+ <th>Weight</th>
116
+ <th>Day</th>
117
+ <th>YTD</th>
118
+ <th>Thesis</th>
119
+ </tr>
120
+ </thead>
121
+ <tbody>
122
+ {HOLDINGS.map((h) => (
123
+ <Row
124
+ key={h.t}
125
+ {...h}
126
+ selected={h.t === selected}
127
+ onSelect={() => setSelected(h.t === selected ? null : h.t)}
128
+ />
129
+ ))}
130
+ </tbody>
131
+ </table>
132
+ <p className="artifact-caption">
133
+ <strong>Pull-based context:</strong> click a holding to select it
134
+ (no page navigation), then ask the assistant{' '}
135
+ <em>"what's the thesis on this one?"</em>. The selection reaches the
136
+ agent via <code>getPageContext</code>, which the widget pulls fresh
137
+ at the start of every turn.
138
+ </p>
139
+ </main>
140
+
141
+ {/* THE PANEL — the host's box. The agent renders inline inside it, sized by CSS.
142
+ Showing/hiding this panel is the open/close control; no floating launcher. */}
143
+ <aside>
144
+ <div className="panel-head">
145
+ <span className="mf">✦</span> matterfact assistant
146
+ </div>
147
+ <div className="agent-slot">
148
+ <MatterfactAgent
149
+ inline
150
+ publishableKey={config.publishableKey}
151
+ widgetOrigin={config.widgetOrigin}
152
+ surface={config.surface}
153
+ theme={config.theme}
154
+ // Pulled fresh every turn — the whole point of this example.
155
+ getPageContext={getPageContext}
156
+ style={{ width: '100%', height: '100%' }}
157
+ />
158
+ </div>
159
+ </aside>
160
+ </div>
161
+ </div>
162
+ );
163
+ }
164
+
165
+ function Kpi({
166
+ label,
167
+ value,
168
+ tone,
169
+ }: {
170
+ label: string;
171
+ value: string;
172
+ tone?: 'pos' | 'neg';
173
+ }) {
174
+ return (
175
+ <div className="kpi">
176
+ <div className="label">{label}</div>
177
+ <div className={`val ${tone ?? ''}`}>{value}</div>
178
+ </div>
179
+ );
180
+ }
181
+
182
+ function Row(props: {
183
+ t: string;
184
+ w: string;
185
+ d: string;
186
+ y: string;
187
+ th: string;
188
+ dn?: boolean;
189
+ yn?: boolean;
190
+ selected?: boolean;
191
+ onSelect?: () => void;
192
+ }) {
193
+ return (
194
+ <tr
195
+ onClick={props.onSelect}
196
+ aria-pressed={props.selected}
197
+ style={{
198
+ cursor: 'pointer',
199
+ background: props.selected ? 'var(--surface-2)' : undefined,
200
+ }}
201
+ >
202
+ <td className="tick">
203
+ {props.selected ? '▸ ' : ''}
204
+ {props.t}
205
+ </td>
206
+ <td>{props.w}</td>
207
+ <td className={props.dn ? 'neg' : 'pos'}>{props.d}</td>
208
+ <td className={props.yn ? 'neg' : 'pos'}>{props.y}</td>
209
+ <td>{props.th}</td>
210
+ </tr>
211
+ );
212
+ }
213
+
214
+ function Setup({ missing }: { missing: string[] }) {
215
+ return (
216
+ <div className="setup">
217
+ <div className="box">
218
+ <h2>Finish the config first</h2>
219
+ <p>
220
+ Copy <code>.env.example</code> to <code>.env</code>, set the values
221
+ below, then restart
222
+ <code>npm run dev</code>.
223
+ </p>
224
+ <ul>
225
+ {missing.map((m) => (
226
+ <li key={m} className="bad">
227
+ ✗ {m}
228
+ </li>
229
+ ))}
230
+ </ul>
231
+ <p>
232
+ The publishable key must have <code>http://localhost:8000</code>{' '}
233
+ registered as an allowed origin on the target deployment, or the agent
234
+ renders nothing.
235
+ </p>
236
+ </div>
237
+ </div>
238
+ );
239
+ }
@@ -0,0 +1,43 @@
1
+ // All demo config comes from Vite env vars (VITE_*), so the components stay clean
2
+ // and nothing lives in the source. See .env.example.
3
+
4
+ type Theme = 'light' | 'dark' | 'auto';
5
+
6
+ // MF_WIDGET_ORIGIN always wins over this table.
7
+ const ORIGINS: Record<string, string> = {
8
+ ecm: 'https://emc.matterfact.com',
9
+ prod: 'https://app.matterfact.com',
10
+ staging: '', // no fixed host — set VITE_MF_WIDGET_ORIGIN
11
+ local: 'http://localhost:3000', // wherever your dev server serves /embed/chat
12
+ };
13
+
14
+ const env = import.meta.env;
15
+ const envName = (env.VITE_MF_ENV ?? 'ecm').toLowerCase();
16
+
17
+ export const config = {
18
+ env: envName,
19
+ widgetOrigin: (env.VITE_MF_WIDGET_ORIGIN || ORIGINS[envName] || '').replace(
20
+ /\/+$/,
21
+ '',
22
+ ),
23
+ publishableKey: env.VITE_MF_PUBLISHABLE_KEY ?? '',
24
+ artifact: {
25
+ slug: env.VITE_MF_ARTIFACT_SLUG ?? '',
26
+ owner: env.VITE_MF_ARTIFACT_OWNER ?? '',
27
+ token: env.VITE_MF_ARTIFACT_TOKEN ?? '',
28
+ },
29
+ surface: env.VITE_MF_SURFACE ?? 'embed-demo',
30
+ theme: (env.VITE_MF_THEME ?? 'light') as Theme,
31
+ };
32
+
33
+ /** Which required values are still missing — drives the setup screen. */
34
+ export function missingConfig(): string[] {
35
+ const miss: string[] = [];
36
+ if (!config.widgetOrigin)
37
+ miss.push('VITE_MF_ENV / VITE_MF_WIDGET_ORIGIN (widget origin)');
38
+ if (!config.publishableKey) miss.push('VITE_MF_PUBLISHABLE_KEY (agent key)');
39
+ if (!config.artifact.slug) miss.push('VITE_MF_ARTIFACT_SLUG');
40
+ if (!config.artifact.owner) miss.push('VITE_MF_ARTIFACT_OWNER');
41
+ if (!config.artifact.token) miss.push('VITE_MF_ARTIFACT_TOKEN');
42
+ return miss;
43
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+ import './styles.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );