@cosmoops/connect-sdk 0.7.0 → 0.9.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/CHANGELOG.md +165 -0
- package/README.md +273 -91
- package/dist/cjs/client/components/access-gate.js +39 -4
- package/dist/cjs/client/components/ai-format.js +135 -0
- package/dist/cjs/client/components/ai-widgets.js +232 -0
- package/dist/cjs/client/components/app-shell.js +7 -6
- package/dist/cjs/client/components/ask-ai.js +334 -0
- package/dist/cjs/client/components/nav.js +1 -1
- package/dist/cjs/client/components/side-nav.js +1 -1
- package/dist/cjs/client/components/user-menu.js +28 -5
- package/dist/cjs/client/components/workspace-type-gate.js +48 -0
- package/dist/cjs/client/index.js +31 -3
- package/dist/cjs/client/provider.js +45 -2
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/server/handler.js +43 -0
- package/dist/cjs/server/http.js +46 -24
- package/dist/cjs/server/resources/entitlements.js +41 -0
- package/dist/cjs/server/resources/identity.js +2 -0
- package/dist/esm/client/components/access-gate.js +40 -5
- package/dist/esm/client/components/ai-format.js +129 -0
- package/dist/esm/client/components/ai-widgets.js +223 -0
- package/dist/esm/client/components/app-shell.js +6 -5
- package/dist/esm/client/components/ask-ai.js +331 -0
- package/dist/esm/client/components/nav.js +1 -1
- package/dist/esm/client/components/side-nav.js +1 -1
- package/dist/esm/client/components/user-menu.js +28 -5
- package/dist/esm/client/components/workspace-type-gate.js +45 -0
- package/dist/esm/client/index.js +13 -2
- package/dist/esm/client/provider.js +43 -2
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/server/handler.js +43 -0
- package/dist/esm/server/http.js +46 -24
- package/dist/esm/server/resources/entitlements.js +41 -0
- package/dist/esm/server/resources/identity.js +2 -0
- package/dist/theme.css +87 -0
- package/dist/types/client/components/access-gate.d.ts +17 -3
- package/dist/types/client/components/ai-format.d.ts +24 -0
- package/dist/types/client/components/ai-widgets.d.ts +42 -0
- package/dist/types/client/components/app-shell.d.ts +21 -5
- package/dist/types/client/components/ask-ai.d.ts +73 -0
- package/dist/types/client/components/nav.d.ts +3 -3
- package/dist/types/client/components/side-nav.d.ts +1 -1
- package/dist/types/client/components/user-menu.d.ts +2 -1
- package/dist/types/client/components/workspace-type-gate.d.ts +48 -0
- package/dist/types/client/index.d.ts +10 -3
- package/dist/types/client/provider.d.ts +44 -1
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/server/http.d.ts +6 -1
- package/dist/types/server/resources/entitlements.d.ts +33 -1
- package/dist/types/types.d.ts +174 -0
- package/package.json +8 -2
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ConnectAskAi = ConnectAskAi;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const react_2 = require("@heroui/react");
|
|
8
|
+
const lucide_react_1 = require("lucide-react");
|
|
9
|
+
const streamdown_1 = require("streamdown");
|
|
10
|
+
const provider_js_1 = require("../provider.js");
|
|
11
|
+
const ai_widgets_js_1 = require("./ai-widgets.js");
|
|
12
|
+
const PANEL_WIDTH = "w-full lg:w-[400px]";
|
|
13
|
+
/**
|
|
14
|
+
* `blurIn` over the default `fadeIn`: Gemini arrives in bursts rather than an
|
|
15
|
+
* even token drip, and a blur masks a batch landing at once where a plain
|
|
16
|
+
* opacity fade just flickers. Hoisted to module scope because Streamdown
|
|
17
|
+
* compares `animated` by reference.
|
|
18
|
+
*/
|
|
19
|
+
const ANIMATED = {
|
|
20
|
+
animation: "blurIn",
|
|
21
|
+
duration: 220,
|
|
22
|
+
easing: "ease-out",
|
|
23
|
+
sep: "word",
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The "working on it" state - a highlight sweeping through the word rather
|
|
27
|
+
* than a spinner beside it. `connect-ai-shimmer` is defined in the SDK's
|
|
28
|
+
* `theme.css`, which every application platform already imports.
|
|
29
|
+
*/
|
|
30
|
+
function ThinkingShimmer({ label = "Thinking" }) {
|
|
31
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: "connect-ai-shimmer text-xs font-medium", role: "status", children: label }));
|
|
32
|
+
}
|
|
33
|
+
/** Open while it streams, collapsed once the answer lands. */
|
|
34
|
+
function ThinkingDisclosure({ text, isStreaming }) {
|
|
35
|
+
if (!text)
|
|
36
|
+
return null;
|
|
37
|
+
return ((0, jsx_runtime_1.jsxs)("details", { className: "group mb-2", open: isStreaming, children: [(0, jsx_runtime_1.jsxs)("summary", { className: "flex cursor-pointer list-none items-center gap-1 text-[11px] font-medium text-muted select-none hover:text-foreground", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Brain, { className: "size-3" }), isStreaming ? (0, jsx_runtime_1.jsx)(ThinkingShimmer, {}) : "Thinking", (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "size-3 transition-transform group-open:rotate-180" })] }), (0, jsx_runtime_1.jsx)("div", { className: "mt-1.5 border-s-2 border-border ps-2 text-xs whitespace-pre-wrap text-muted italic", children: text })] }));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Structural guards, one per widget.
|
|
41
|
+
*
|
|
42
|
+
* The published package carries no schema validator, and should not gain one
|
|
43
|
+
* for this - so each tool input is checked for the fields its renderer would
|
|
44
|
+
* otherwise crash on. Anything that fails renders nothing: the model's prose
|
|
45
|
+
* sits around it either way, so a missing widget reads as terse rather than
|
|
46
|
+
* broken, which is the better failure.
|
|
47
|
+
*/
|
|
48
|
+
const isArray = (value) => Array.isArray(value) && value.length > 0;
|
|
49
|
+
const field = (input, key) => typeof input === "object" && input !== null ? input[key] : undefined;
|
|
50
|
+
function renderTool(name, input, renderMarkdown) {
|
|
51
|
+
switch (name) {
|
|
52
|
+
case "showTable":
|
|
53
|
+
return isArray(field(input, "columns")) && Array.isArray(field(input, "rows")) ? ((0, jsx_runtime_1.jsx)(ai_widgets_js_1.AiTable, { ...input })) : null;
|
|
54
|
+
case "showChart":
|
|
55
|
+
return isArray(field(input, "series")) ? (0, jsx_runtime_1.jsx)(ai_widgets_js_1.AiChart, { ...input }) : null;
|
|
56
|
+
case "showMetrics":
|
|
57
|
+
return isArray(field(input, "metrics")) ? (0, jsx_runtime_1.jsx)(ai_widgets_js_1.AiMetrics, { ...input }) : null;
|
|
58
|
+
case "showTimeline":
|
|
59
|
+
return isArray(field(input, "events")) ? (0, jsx_runtime_1.jsx)(ai_widgets_js_1.AiTimeline, { ...input }) : null;
|
|
60
|
+
case "showDetails":
|
|
61
|
+
return isArray(field(input, "fields")) ? (0, jsx_runtime_1.jsx)(ai_widgets_js_1.AiDetails, { ...input }) : null;
|
|
62
|
+
case "showSteps":
|
|
63
|
+
return isArray(field(input, "steps")) ? ((0, jsx_runtime_1.jsx)(ai_widgets_js_1.AiSteps, { ...input, renderDetail: renderMarkdown })) : null;
|
|
64
|
+
case "showSources":
|
|
65
|
+
return isArray(field(input, "sources")) ? (0, jsx_runtime_1.jsx)(ai_widgets_js_1.AiSources, { ...input }) : null;
|
|
66
|
+
default:
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Placeholder while a tool's arguments are still arriving, sized like the real thing. */
|
|
71
|
+
function ToolSkeleton() {
|
|
72
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "my-3 flex h-24 animate-pulse items-center justify-center rounded-lg border border-border bg-surface-secondary", role: "status", "aria-label": "Preparing result", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-4 text-accent/60", "aria-hidden": true }) }));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One server-sent event stream, folded into the turn being built.
|
|
76
|
+
*
|
|
77
|
+
* Hand-rolled rather than pulled from `@ai-sdk/react`, and that is a deliberate
|
|
78
|
+
* trade: this package ships to partners, and `ai` + `@ai-sdk/react` is a large
|
|
79
|
+
* dependency to add to every application platform for one panel. The wire format
|
|
80
|
+
* is small, both ends of it are ours, and the chunk kinds below are all this
|
|
81
|
+
* panel can render.
|
|
82
|
+
*
|
|
83
|
+
* Tool arguments are read from `tool-input-available` - the complete object -
|
|
84
|
+
* rather than reassembled from `tool-input-delta` fragments. `tool-input-start`
|
|
85
|
+
* is enough to put a skeleton on screen in the meantime, and partial-JSON
|
|
86
|
+
* parsing buys nothing a placeholder does not.
|
|
87
|
+
*
|
|
88
|
+
* Chunks are newline-delimited `data:` frames; a frame can split across network
|
|
89
|
+
* reads, so the tail of each read is carried forward rather than parsed.
|
|
90
|
+
*/
|
|
91
|
+
async function readStream(response, onChunk) {
|
|
92
|
+
const body = response.body;
|
|
93
|
+
if (!body)
|
|
94
|
+
throw new Error("Ask AI returned no stream");
|
|
95
|
+
const reader = body.pipeThrough(new TextDecoderStream()).getReader();
|
|
96
|
+
let buffer = "";
|
|
97
|
+
for (;;) {
|
|
98
|
+
const { done, value } = await reader.read();
|
|
99
|
+
if (done)
|
|
100
|
+
break;
|
|
101
|
+
buffer += value;
|
|
102
|
+
// Everything up to the last newline is whole; whatever follows it is a
|
|
103
|
+
// partial frame and waits for the next read.
|
|
104
|
+
const lines = buffer.split("\n");
|
|
105
|
+
buffer = lines.pop() ?? "";
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
if (!line.startsWith("data:"))
|
|
108
|
+
continue;
|
|
109
|
+
const payload = line.slice(5).trim();
|
|
110
|
+
if (!payload || payload === "[DONE]")
|
|
111
|
+
continue;
|
|
112
|
+
let chunk;
|
|
113
|
+
try {
|
|
114
|
+
chunk = JSON.parse(payload);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// A frame we cannot parse is a frame we cannot render. Dropping it
|
|
118
|
+
// keeps the rest of the answer flowing, which is strictly better than
|
|
119
|
+
// failing the whole turn over one malformed chunk.
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
switch (chunk.type) {
|
|
123
|
+
case "text-delta":
|
|
124
|
+
if (chunk.delta)
|
|
125
|
+
onChunk({ text: chunk.delta });
|
|
126
|
+
break;
|
|
127
|
+
case "reasoning-delta":
|
|
128
|
+
if (chunk.delta)
|
|
129
|
+
onChunk({ reasoning: chunk.delta });
|
|
130
|
+
break;
|
|
131
|
+
case "tool-input-start":
|
|
132
|
+
if (chunk.toolCallId && chunk.toolName) {
|
|
133
|
+
onChunk({ toolStart: { id: chunk.toolCallId, name: chunk.toolName } });
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case "tool-input-available":
|
|
137
|
+
if (chunk.toolCallId)
|
|
138
|
+
onChunk({ toolInput: { id: chunk.toolCallId, input: chunk.input } });
|
|
139
|
+
break;
|
|
140
|
+
case "error":
|
|
141
|
+
throw new Error(chunk.errorText || "Ask AI failed");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Index of the final text block, or `-1`. A reverse loop rather than
|
|
148
|
+
* `findLastIndex`, which is ES2023 - this package targets ES2022 so its
|
|
149
|
+
* published output stays usable on the runtimes its `engines` field claims.
|
|
150
|
+
*/
|
|
151
|
+
function lastTextBlockIndex(blocks) {
|
|
152
|
+
for (let index = blocks.length - 1; index >= 0; index--) {
|
|
153
|
+
if (blocks[index]?.kind === "text")
|
|
154
|
+
return index;
|
|
155
|
+
}
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
/** Appends a text delta to the open text block, or opens one after a widget. */
|
|
159
|
+
function appendText(blocks, text) {
|
|
160
|
+
const last = blocks.at(-1);
|
|
161
|
+
if (last?.kind === "text") {
|
|
162
|
+
return [...blocks.slice(0, -1), { kind: "text", text: last.text + text }];
|
|
163
|
+
}
|
|
164
|
+
return [...blocks, { kind: "text", text }];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Ask AI - a chat panel grounded in the signed-in workspace's Context Index,
|
|
168
|
+
* streamed through your own bridge.
|
|
169
|
+
*
|
|
170
|
+
* Answers are not only prose. Connect's model can call a small set of
|
|
171
|
+
* presentation tools - `showTable`, `showChart`, `showMetrics`, `showTimeline`,
|
|
172
|
+
* `showDetails`, `showSteps`, `showSources` - and this panel renders each one
|
|
173
|
+
* natively: a sortable table with a CSV download, a chart, a row of metric
|
|
174
|
+
* tiles. Values arrive raw with a format tag, so they are formatted here rather
|
|
175
|
+
* than by the model, which is what lets a column sort numerically.
|
|
176
|
+
*
|
|
177
|
+
* Nothing here holds a credential: it POSTs to `${basePath}/ai/chat` on your
|
|
178
|
+
* own origin, and `createConnectHandler` is what reaches Connect. The workspace
|
|
179
|
+
* is resolved from the caller's session there, never sent from the browser - so
|
|
180
|
+
* a user can only ever ask about the workspace they are signed into.
|
|
181
|
+
*
|
|
182
|
+
* ```tsx
|
|
183
|
+
* <AppShell name="Dispatch" nav={nav}>
|
|
184
|
+
* {children}
|
|
185
|
+
* </AppShell>
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* `AppShell` mounts it by default. Mount it directly only when you are not
|
|
189
|
+
* using the shell: it renders a fixed corner trigger plus a panel that is an
|
|
190
|
+
* *in-flow flex sibling* at `lg` and up, so opening it pushes your content
|
|
191
|
+
* aside rather than covering it. That requires it to be the last child of a
|
|
192
|
+
* `flex` row owning the viewport height - which is exactly what `AppShell` is.
|
|
193
|
+
* Below `lg` it is a full-screen sheet with a backdrop instead; there is no
|
|
194
|
+
* room to push anything on a phone.
|
|
195
|
+
*
|
|
196
|
+
* **Host setup:** markdown rendering needs `streamdown` installed, one Tailwind
|
|
197
|
+
* `@source "…/node_modules/streamdown/dist/*.js"` line so its utility classes
|
|
198
|
+
* are generated, and `import "streamdown/styles.css"` for the streaming
|
|
199
|
+
* animation. The colour tokens Streamdown expects are already mapped in this
|
|
200
|
+
* SDK's `theme.css`.
|
|
201
|
+
*
|
|
202
|
+
* Answers require the workspace's Context Index to be turned on (a workspace
|
|
203
|
+
* admin toggle in Connect) and your API key to carry the `ai:chat` scope.
|
|
204
|
+
* Neither is something the browser can discover, so both surface as a message
|
|
205
|
+
* in the panel rather than as a hidden button.
|
|
206
|
+
*/
|
|
207
|
+
function ConnectAskAi({ title = "Ask AI", assistantName = "Assistant", placeholder = "Ask a question...", hint = "Ask a question about your workspace - answers are grounded in what your team has indexed.", defaultOpen = false, markdownPlugins, }) {
|
|
208
|
+
const { session, isLoading, streamResource } = (0, provider_js_1.useConnect)();
|
|
209
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(defaultOpen);
|
|
210
|
+
const [turns, setTurns] = (0, react_1.useState)([]);
|
|
211
|
+
const [input, setInput] = (0, react_1.useState)("");
|
|
212
|
+
const [isBusy, setIsBusy] = (0, react_1.useState)(false);
|
|
213
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
214
|
+
const abortRef = (0, react_1.useRef)(null);
|
|
215
|
+
const bottomRef = (0, react_1.useRef)(null);
|
|
216
|
+
// Ids only have to be unique within one mounted conversation, so a counter is
|
|
217
|
+
// enough - and unlike `crypto.randomUUID()` it cannot differ between the
|
|
218
|
+
// server's render and the browser's.
|
|
219
|
+
const nextId = (0, react_1.useRef)(0);
|
|
220
|
+
(0, react_1.useEffect)(() => {
|
|
221
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
|
|
222
|
+
}, [turns]);
|
|
223
|
+
// A conversation left mid-stream should stop costing tokens the moment the
|
|
224
|
+
// panel goes away.
|
|
225
|
+
(0, react_1.useEffect)(() => () => abortRef.current?.abort(), []);
|
|
226
|
+
const renderMarkdown = (0, react_1.useCallback)((text, isStreaming = false) => ((0, jsx_runtime_1.jsx)(streamdown_1.Streamdown, { className: "connect-ai-body", mode: isStreaming ? "streaming" : "static", isAnimating: isStreaming, animated: ANIMATED, caret: isStreaming ? "block" : undefined, plugins: markdownPlugins,
|
|
227
|
+
// Retrieved documents cite real URLs; an interstitial on every one of
|
|
228
|
+
// them turns a citation into a chore. The renderer still sanitises the
|
|
229
|
+
// markup itself, which is the part that matters.
|
|
230
|
+
linkSafety: LINK_SAFETY, children: text })), [markdownPlugins]);
|
|
231
|
+
const stop = (0, react_1.useCallback)(() => {
|
|
232
|
+
abortRef.current?.abort();
|
|
233
|
+
abortRef.current = null;
|
|
234
|
+
setIsBusy(false);
|
|
235
|
+
}, []);
|
|
236
|
+
const send = (0, react_1.useCallback)(async (text) => {
|
|
237
|
+
const controller = new AbortController();
|
|
238
|
+
abortRef.current = controller;
|
|
239
|
+
setError(null);
|
|
240
|
+
setIsBusy(true);
|
|
241
|
+
const userTurn = {
|
|
242
|
+
id: `u${nextId.current++}`,
|
|
243
|
+
role: "user",
|
|
244
|
+
blocks: [{ kind: "text", text }],
|
|
245
|
+
reasoning: "",
|
|
246
|
+
};
|
|
247
|
+
const answerId = `a${nextId.current++}`;
|
|
248
|
+
// The wire format Connect reads - part-based `UIMessage`s, not flat
|
|
249
|
+
// strings. Built from the turns *plus* this one rather than from state,
|
|
250
|
+
// which has not been committed yet at this point. Only text is sent back:
|
|
251
|
+
// a widget is a rendering of something the model already said.
|
|
252
|
+
const history = [...turns, userTurn].map((turn) => ({
|
|
253
|
+
id: turn.id,
|
|
254
|
+
role: turn.role,
|
|
255
|
+
parts: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: turn.blocks
|
|
259
|
+
.filter((block) => block.kind === "text")
|
|
260
|
+
.map((block) => block.text)
|
|
261
|
+
.join(""),
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
}));
|
|
265
|
+
setTurns((current) => [...current, userTurn]);
|
|
266
|
+
const patchAnswer = (patch) => setTurns((current) => current.map((turn) => (turn.id === answerId ? patch(turn) : turn)));
|
|
267
|
+
try {
|
|
268
|
+
// Relative to the provider's own `basePath` - `streamResource` prefixes
|
|
269
|
+
// it, the same way every other bridge call is addressed.
|
|
270
|
+
const response = await streamResource("/ai/chat", { messages: history }, { signal: controller.signal });
|
|
271
|
+
// The assistant's turn is appended only once the stream is live, so a
|
|
272
|
+
// failed request leaves an error under the question rather than an
|
|
273
|
+
// empty bubble that never fills.
|
|
274
|
+
setTurns((current) => [...current, { id: answerId, role: "assistant", blocks: [], reasoning: "" }]);
|
|
275
|
+
await readStream(response, (chunk) => {
|
|
276
|
+
if (chunk.text) {
|
|
277
|
+
patchAnswer((turn) => ({ ...turn, blocks: appendText(turn.blocks, chunk.text) }));
|
|
278
|
+
}
|
|
279
|
+
if (chunk.reasoning) {
|
|
280
|
+
patchAnswer((turn) => ({ ...turn, reasoning: turn.reasoning + chunk.reasoning }));
|
|
281
|
+
}
|
|
282
|
+
if (chunk.toolStart) {
|
|
283
|
+
const { id, name } = chunk.toolStart;
|
|
284
|
+
patchAnswer((turn) => ({ ...turn, blocks: [...turn.blocks, { kind: "tool", id, name, input: null }] }));
|
|
285
|
+
}
|
|
286
|
+
if (chunk.toolInput) {
|
|
287
|
+
const { id, input: toolInput } = chunk.toolInput;
|
|
288
|
+
patchAnswer((turn) => ({
|
|
289
|
+
...turn,
|
|
290
|
+
blocks: turn.blocks.map((block) => block.kind === "tool" && block.id === id ? { ...block, input: toolInput } : block),
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (cause) {
|
|
296
|
+
// Pressing Stop aborts the fetch, which lands here - a decision, not a
|
|
297
|
+
// failure, and nothing to report.
|
|
298
|
+
if (!controller.signal.aborted) {
|
|
299
|
+
setError(cause instanceof Error ? cause.message : "Ask AI failed");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
if (abortRef.current === controller)
|
|
304
|
+
abortRef.current = null;
|
|
305
|
+
setIsBusy(false);
|
|
306
|
+
}
|
|
307
|
+
}, [streamResource, turns]);
|
|
308
|
+
const lastTurn = turns.at(-1);
|
|
309
|
+
const isAnswering = isBusy && lastTurn?.role === "assistant";
|
|
310
|
+
const rendered = (0, react_1.useMemo)(() => turns.map((turn) => {
|
|
311
|
+
const isLive = isAnswering && turn.id === lastTurn?.id;
|
|
312
|
+
const lastTextIndex = lastTextBlockIndex(turn.blocks);
|
|
313
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `connect-ai-host rounded-md border border-border p-2.5 text-sm ${turn.role === "user" ? "bg-surface-secondary" : "bg-accent/5"}`, children: [(0, jsx_runtime_1.jsx)("p", { className: "mb-1 text-[11px] font-semibold tracking-wide text-muted uppercase", children: turn.role === "user" ? "You" : assistantName }), turn.role === "assistant" && ((0, jsx_runtime_1.jsx)(ThinkingDisclosure, { text: turn.reasoning, isStreaming: isLive && lastTextIndex === -1 })), turn.blocks.map((block, index) => block.kind === "text" ? (
|
|
314
|
+
// The user's own turn is shown as they typed it - running a
|
|
315
|
+
// question that happens to start with `#` through the markdown
|
|
316
|
+
// renderer would turn it into a heading.
|
|
317
|
+
(0, jsx_runtime_1.jsx)("div", { children: turn.role === "user" ? ((0, jsx_runtime_1.jsx)("p", { className: "whitespace-pre-wrap", children: block.text })) : (renderMarkdown(block.text, isLive && index === lastTextIndex)) }, index)) : block.input === null ? ((0, jsx_runtime_1.jsx)(ToolSkeleton, {}, block.id)) : ((0, jsx_runtime_1.jsx)("div", { children: renderTool(block.name, block.input, renderMarkdown) }, block.id))), isLive && lastTextIndex === -1 && turn.blocks.length === 0 && (0, jsx_runtime_1.jsx)(ThinkingShimmer, {})] }, turn.id));
|
|
318
|
+
}), [turns, isAnswering, lastTurn?.id, assistantName, renderMarkdown]);
|
|
319
|
+
// Signed out there is no workspace to ground an answer in, and the bridge
|
|
320
|
+
// would answer 401 - so there is nothing to offer, not even a button.
|
|
321
|
+
if (isLoading || !session?.user)
|
|
322
|
+
return null;
|
|
323
|
+
const handleSubmit = (event) => {
|
|
324
|
+
event.preventDefault();
|
|
325
|
+
const text = input.trim();
|
|
326
|
+
if (!text || isBusy)
|
|
327
|
+
return;
|
|
328
|
+
setInput("");
|
|
329
|
+
void send(text);
|
|
330
|
+
};
|
|
331
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!isOpen && ((0, jsx_runtime_1.jsx)(react_2.Button, { isIconOnly: true, variant: "primary", "aria-label": title, onPress: () => setIsOpen(true), className: "fixed bottom-4 end-4 z-30 size-12 rounded-full shadow-lg sm:bottom-6 sm:end-6", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-5" }) })), (0, jsx_runtime_1.jsx)("div", { className: `fixed inset-0 z-40 bg-backdrop transition-opacity lg:hidden ${isOpen ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"}`, onClick: () => setIsOpen(false), "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("aside", { "aria-hidden": !isOpen, className: `fixed inset-y-0 end-0 z-50 flex flex-col border-s border-border bg-surface shadow-xl transition-transform duration-300 ease-in-out ${PANEL_WIDTH} ${isOpen ? "translate-x-0" : "translate-x-full rtl:-translate-x-full"} lg:static lg:z-auto lg:translate-x-0 lg:shadow-none lg:transition-[width] ${isOpen ? "" : "lg:w-0 lg:overflow-hidden lg:border-s-0"}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `flex h-full shrink-0 flex-col ${PANEL_WIDTH}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-4 shrink-0 text-accent" }), (0, jsx_runtime_1.jsx)("span", { className: "flex-1 text-sm font-semibold", children: title }), (0, jsx_runtime_1.jsx)(react_2.Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Close", onPress: () => setIsOpen(false), children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "size-4" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [turns.length === 0 && (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: hint }), rendered, isBusy && lastTurn?.role === "user" && ((0, jsx_runtime_1.jsxs)("div", { className: "rounded-md border border-border bg-accent/5 p-2.5 text-sm", children: [(0, jsx_runtime_1.jsx)("p", { className: "mb-1 text-[11px] font-semibold tracking-wide text-muted uppercase", children: assistantName }), (0, jsx_runtime_1.jsx)(ThinkingShimmer, {})] })), error && (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-danger", children: error }), (0, jsx_runtime_1.jsx)("div", { ref: bottomRef })] }), (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "flex gap-2 border-t border-border p-3", children: [(0, jsx_runtime_1.jsxs)(react_2.TextField, { variant: "secondary", className: "flex-1", children: [(0, jsx_runtime_1.jsx)(react_2.Label, { className: "sr-only", children: placeholder }), (0, jsx_runtime_1.jsx)(react_2.Input, { placeholder: placeholder, value: input, onChange: (event) => setInput(event.currentTarget.value), disabled: isBusy })] }), isBusy ? ((0, jsx_runtime_1.jsx)(react_2.Button, { type: "button", isIconOnly: true, variant: "secondary", "aria-label": "Stop generating", onPress: stop, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Square, { className: "size-3.5" }) })) : ((0, jsx_runtime_1.jsx)(react_2.Button, { type: "submit", isIconOnly: true, variant: "primary", "aria-label": "Send", isDisabled: !input.trim(), children: (0, jsx_runtime_1.jsx)(lucide_react_1.Send, { className: "size-3.5" }) }))] })] }) })] }));
|
|
332
|
+
}
|
|
333
|
+
/** Hoisted for the same reference-equality reason as `ANIMATED` - Streamdown compares it by identity. */
|
|
334
|
+
const LINK_SAFETY = { enabled: false };
|
|
@@ -89,7 +89,7 @@ function isNavItemActive(item, activeHref) {
|
|
|
89
89
|
return false;
|
|
90
90
|
return normalizePath(item.href) === activeHref;
|
|
91
91
|
}
|
|
92
|
-
/** Hrefs declared `gateExempt` - the routes `
|
|
92
|
+
/** Hrefs declared `gateExempt` - the routes `AppShell` renders outside its `ConnectAccessGate`. */
|
|
93
93
|
function gateExemptPaths(nav) {
|
|
94
94
|
return flattenNav(nav)
|
|
95
95
|
.filter((item) => item.gateExempt)
|
|
@@ -39,7 +39,7 @@ function NavRow({ item, isActive, hasActiveChild, collapsed, depth, renderLink,
|
|
|
39
39
|
* The sidebar's nav column on its own - sections, items, one level of
|
|
40
40
|
* children, and the active highlight.
|
|
41
41
|
*
|
|
42
|
-
* `
|
|
42
|
+
* `AppShell` renders this inside its rail and its mobile drawer, and it
|
|
43
43
|
* is exported for a host that wants the shell's navigation without its
|
|
44
44
|
* chrome: the same items, the same active resolution, dropped into a layout
|
|
45
45
|
* you already have.
|
|
@@ -27,6 +27,28 @@ function initials(name, email) {
|
|
|
27
27
|
function openExternal(url) {
|
|
28
28
|
window.open(url, "_blank", "noopener,noreferrer");
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Connect's profile page, with the context it needs to offer a way back here.
|
|
32
|
+
*
|
|
33
|
+
* Connect answers `/profile` for any signed-in user, so the bare URL works on
|
|
34
|
+
* its own - these two params only add the "Back to <your app>" banner. It
|
|
35
|
+
* validates `return_to` against this `client_id`'s registered redirect
|
|
36
|
+
* origins and silently drops the banner if they don't match, so a wrong or
|
|
37
|
+
* stale `appUrl`/`clientId` pairing degrades to what it did before rather
|
|
38
|
+
* than erroring.
|
|
39
|
+
*
|
|
40
|
+
* `window.location.href` rather than a configured home URL: the point is to
|
|
41
|
+
* come back to the page they left, and only the browser knows which one that
|
|
42
|
+
* is.
|
|
43
|
+
*/
|
|
44
|
+
function profileUrl(appUrl, clientId) {
|
|
45
|
+
const url = new URL("/profile", appUrl);
|
|
46
|
+
url.searchParams.set("client_id", clientId);
|
|
47
|
+
if (typeof window !== "undefined") {
|
|
48
|
+
url.searchParams.set("return_to", window.location.href);
|
|
49
|
+
}
|
|
50
|
+
return url.toString();
|
|
51
|
+
}
|
|
30
52
|
/**
|
|
31
53
|
* The member's photo, with their initials underneath it.
|
|
32
54
|
*
|
|
@@ -122,7 +144,7 @@ function DestinationTile({ icon, label, url, }) {
|
|
|
122
144
|
* the modal programmatically; sign-out uses `slot="close"` instead.
|
|
123
145
|
*/
|
|
124
146
|
function ConnectUserMenu({ compact = false, onSignOut, className, theme, onThemeChange, }) {
|
|
125
|
-
const { session, isLoading, signOut, appUrl } = (0, provider_js_1.useConnect)();
|
|
147
|
+
const { session, isLoading, signOut, appUrl, clientId } = (0, provider_js_1.useConnect)();
|
|
126
148
|
if (isLoading) {
|
|
127
149
|
return ((0, jsx_runtime_1.jsxs)("div", { className: `flex items-center gap-2 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "size-8 shrink-0 rounded-full" }), !compact && (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-4 w-24" })] }));
|
|
128
150
|
}
|
|
@@ -136,7 +158,7 @@ function ConnectUserMenu({ compact = false, onSignOut, className, theme, onTheme
|
|
|
136
158
|
// `pl-1` against the default right padding is optical, not sloppy: a
|
|
137
159
|
// circular avatar needs less inset than a text label to look evenly
|
|
138
160
|
// seated in the button.
|
|
139
|
-
className: `gap-2 pl-1 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "sm" }), !compact && (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-foreground", children: label })] }), (0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { size: "md", scroll: "inside", children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: "Account" }) }), (0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { className: "flex flex-col gap-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: `flex items-start gap-3 rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} p-4`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "lg" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.email }), (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.workspaceName }), (role || planChip || !user.emailVerified) && ((0, jsx_runtime_1.jsxs)("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [role && ((0, jsx_runtime_1.jsx)(react_1.Chip, { size: "sm", variant: "soft", color: "accent", children: humanizeRole(role) })), planChip, !user.emailVerified && ((0, jsx_runtime_1.jsx)(react_1.Chip, { size: "sm", variant: "soft", color: "warning", children: "Email unverified" }))] }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [(0, jsx_runtime_1.jsx)(DestinationTile, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.UserRound, { className: surfaces_js_1.ICON, "aria-hidden": "true" }), label: "Profile", url:
|
|
161
|
+
className: `gap-2 pl-1 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "sm" }), !compact && (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-foreground", children: label })] }), (0, jsx_runtime_1.jsx)(react_1.Modal.Backdrop, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Container, { size: "md", scroll: "inside", children: (0, jsx_runtime_1.jsxs)(react_1.Modal.Dialog, { children: [(0, jsx_runtime_1.jsx)(react_1.Modal.CloseTrigger, {}), (0, jsx_runtime_1.jsx)(react_1.Modal.Header, { children: (0, jsx_runtime_1.jsx)(react_1.Modal.Heading, { children: "Account" }) }), (0, jsx_runtime_1.jsxs)(react_1.Modal.Body, { className: "flex flex-col gap-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: `flex items-start gap-3 rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} p-4`, children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "lg" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm font-semibold text-foreground", children: label }), user.email && (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.email }), (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: user.workspaceName }), (role || planChip || !user.emailVerified) && ((0, jsx_runtime_1.jsxs)("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [role && ((0, jsx_runtime_1.jsx)(react_1.Chip, { size: "sm", variant: "soft", color: "accent", children: humanizeRole(role) })), planChip, !user.emailVerified && ((0, jsx_runtime_1.jsx)(react_1.Chip, { size: "sm", variant: "soft", color: "warning", children: "Email unverified" }))] }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-3 gap-2", children: [(0, jsx_runtime_1.jsx)(DestinationTile, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.UserRound, { className: surfaces_js_1.ICON, "aria-hidden": "true" }), label: "Profile", url: profileUrl(appUrl, clientId) }), (0, jsx_runtime_1.jsx)(DestinationTile, { icon: (0, jsx_runtime_1.jsx)(lucide_react_1.CreditCard, { className: surfaces_js_1.ICON, "aria-hidden": "true" }), label: "Billing", url: `${appUrl}/billing` }), (0, jsx_runtime_1.jsx)(DestinationTile, { icon: (0, jsx_runtime_1.jsx)(mark_js_1.ConnectMark, { className: surfaces_js_1.ICON }), label: "Connect", url: appUrl })] }), theme && onThemeChange && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Appearance" }), (0, jsx_runtime_1.jsx)(react_1.ToggleButtonGroup, { fullWidth: true, "aria-label": "Theme", selectionMode: "single", disallowEmptySelection: true, selectedKeys: [theme], onSelectionChange: (keys) => {
|
|
140
162
|
const [next] = [...keys];
|
|
141
163
|
if (typeof next === "string")
|
|
142
164
|
onThemeChange(next);
|
|
@@ -175,13 +197,14 @@ function ConnectInvoicesButton({ className }) {
|
|
|
175
197
|
* `ConnectHomeButton`.
|
|
176
198
|
*
|
|
177
199
|
* It was reachable only from inside `ConnectUserMenu`, which meant an app that
|
|
178
|
-
* builds its own account menu had to hardcode `${appUrl}/profile` to offer it
|
|
200
|
+
* builds its own account menu had to hardcode `${appUrl}/profile` to offer it -
|
|
201
|
+
* and got no `return_to`, so the user had no way back.
|
|
179
202
|
*/
|
|
180
203
|
function ConnectProfileButton({ className }) {
|
|
181
|
-
const { session, appUrl } = (0, provider_js_1.useConnect)();
|
|
204
|
+
const { session, appUrl, clientId } = (0, provider_js_1.useConnect)();
|
|
182
205
|
if (!session?.user)
|
|
183
206
|
return null;
|
|
184
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", className: className, onPress: () => openExternal(
|
|
207
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "tertiary", className: className, onPress: () => openExternal(profileUrl(appUrl, clientId)), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.UserRound, { className: "size-4 shrink-0" }), "Profile", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5 shrink-0 text-muted" })] }));
|
|
185
208
|
}
|
|
186
209
|
/** A plain "Open Connect" button, for a footer or a help menu. */
|
|
187
210
|
function ConnectHomeButton({ className }) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ConnectWorkspaceTypeGate = ConnectWorkspaceTypeGate;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const provider_js_1 = require("../provider.js");
|
|
7
|
+
/**
|
|
8
|
+
* Renders `children` only for a workspace of the given type.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <ConnectWorkspaceTypeGate type="business">
|
|
12
|
+
* <TeamMembersTable />
|
|
13
|
+
* </ConnectWorkspaceTypeGate>
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The point is dead controls, not access: an `"individual"` workspace has
|
|
17
|
+
* exactly one seat and can never have another, so a members table, an invite
|
|
18
|
+
* button and a seat meter are all things that can only ever fail there.
|
|
19
|
+
* Hiding them is a better answer than letting someone press one and read an
|
|
20
|
+
* error about an account shape they did not choose.
|
|
21
|
+
*
|
|
22
|
+
* This is emphatically **not** the audience gate. It does not know what your
|
|
23
|
+
* application is sold to and will happily render a business-only subtree to a
|
|
24
|
+
* business workspace that has no business being in your application at all -
|
|
25
|
+
* that is `<ConnectAccessGate>`'s job, off the `audience_mismatch` readiness
|
|
26
|
+
* blocker. Use this one *inside* an application both audiences can reach, to
|
|
27
|
+
* vary what each sees.
|
|
28
|
+
*
|
|
29
|
+
* Like every gate in this package it is a **UX gate, not a security
|
|
30
|
+
* boundary**: the browser can ignore it. Anything that must not happen for an
|
|
31
|
+
* individual workspace has to be refused on your server too.
|
|
32
|
+
*
|
|
33
|
+
* Renders `loading` - not `fallback` - while the session is unknown, so a
|
|
34
|
+
* business account never flickers through the individual-account layout.
|
|
35
|
+
*/
|
|
36
|
+
function ConnectWorkspaceTypeGate({ type, children, fallback = null, loading = null, }) {
|
|
37
|
+
const { session, isLoading } = (0, provider_js_1.useConnect)();
|
|
38
|
+
const workspaceType = session?.user?.workspaceType ?? null;
|
|
39
|
+
if (isLoading)
|
|
40
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading });
|
|
41
|
+
// Signed out is not "the other type" - there is no workspace to be a type
|
|
42
|
+
// of yet, and rendering the fallback here would put an individual-account
|
|
43
|
+
// layout in front of a visitor who has not signed in.
|
|
44
|
+
if (!workspaceType)
|
|
45
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading });
|
|
46
|
+
const accepted = Array.isArray(type) ? type : [type];
|
|
47
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: accepted.includes(workspaceType) ? children : fallback });
|
|
48
|
+
}
|
package/dist/cjs/client/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.
|
|
5
|
-
exports.resolvePlanGroup = exports.groupPlansByTier = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = exports.resolveAction = exports.monthlyOf = exports.formatPrice = exports.buildCheckoutUrl = exports.useConnectCheckout = exports.ConnectPlansPage = exports.ConnectUsageMeter = exports.useConnectFeature = void 0;
|
|
4
|
+
exports.AI_EMPTY_VALUE = exports.aiStatusTone = exports.isNumericAiFormat = exports.formatAiValue = exports.AiSources = exports.AiSteps = exports.AiDetails = exports.AiTimeline = exports.AiMetrics = exports.AiChart = exports.AiTable = exports.ConnectAskAi = exports.ConnectAppsGrid = exports.gateExemptPaths = exports.isNavItemActive = exports.resolveActivePath = exports.normalizePath = exports.flattenNav = exports.normalizeNav = exports.useActivePath = exports.ConnectSideNav = exports.AppShell = exports.ConnectAccountSessions = exports.ConnectSignOutButton = exports.ConnectHomeButton = exports.ConnectProfileButton = exports.ConnectInvoicesButton = exports.ConnectManageSubscriptionButton = exports.ConnectUserMenu = exports.ConnectRegisterButton = exports.ConnectLoginButton = exports.ConnectLogo = exports.ConnectMark = exports.isConnectError = exports.ConnectError = exports.normalizePlanFeatures = exports.blockingBlockers = exports.CONNECT_APP_URL = exports.useConnectResource = exports.useConnectIntegrations = exports.useConnectSessions = exports.useConnectAddonUsage = exports.useConnectApps = exports.useConnectPlans = exports.useConnectEntitlements = exports.useConnectAudience = exports.useConnectWorkspaceType = exports.useConnectUser = exports.useConnect = exports.ConnectProvider = void 0;
|
|
5
|
+
exports.resolvePlanGroup = exports.groupPlansByTier = exports.ACTION_LABEL = exports.discountLabel = exports.annualSaving = exports.resolveAction = exports.monthlyOf = exports.formatPrice = exports.buildCheckoutUrl = exports.useConnectCheckout = exports.ConnectPlansPage = exports.ConnectUsageMeter = exports.ConnectWorkspaceTypeGate = exports.useConnectFeature = exports.ConnectFeatureGate = exports.useConnectAccessGate = exports.ConnectAccessGate = exports.ConnectReadinessGate = exports.ConnectReadinessBanner = exports.resolveSubscriptionState = exports.ConnectSeatUsage = exports.ConnectTrialBanner = exports.ConnectSubscriptionCard = exports.ConnectNoSubscriptionCard = exports.ConnectSubscriptionGate = exports.ConnectSubscribeDialog = exports.ConnectPlans = exports.ConnectIntegrationsModal = exports.AI_TONE_DOT = exports.AI_TONE_CHIP = void 0;
|
|
6
6
|
/**
|
|
7
7
|
* Browser half of `@cosmoops/connect-sdk` - React components and hooks.
|
|
8
8
|
*
|
|
@@ -19,6 +19,8 @@ var provider_js_1 = require("./provider.js");
|
|
|
19
19
|
Object.defineProperty(exports, "ConnectProvider", { enumerable: true, get: function () { return provider_js_1.ConnectProvider; } });
|
|
20
20
|
Object.defineProperty(exports, "useConnect", { enumerable: true, get: function () { return provider_js_1.useConnect; } });
|
|
21
21
|
Object.defineProperty(exports, "useConnectUser", { enumerable: true, get: function () { return provider_js_1.useConnectUser; } });
|
|
22
|
+
Object.defineProperty(exports, "useConnectWorkspaceType", { enumerable: true, get: function () { return provider_js_1.useConnectWorkspaceType; } });
|
|
23
|
+
Object.defineProperty(exports, "useConnectAudience", { enumerable: true, get: function () { return provider_js_1.useConnectAudience; } });
|
|
22
24
|
Object.defineProperty(exports, "useConnectEntitlements", { enumerable: true, get: function () { return provider_js_1.useConnectEntitlements; } });
|
|
23
25
|
Object.defineProperty(exports, "useConnectPlans", { enumerable: true, get: function () { return provider_js_1.useConnectPlans; } });
|
|
24
26
|
Object.defineProperty(exports, "useConnectApps", { enumerable: true, get: function () { return provider_js_1.useConnectApps; } });
|
|
@@ -59,7 +61,7 @@ Object.defineProperty(exports, "ConnectAccountSessions", { enumerable: true, get
|
|
|
59
61
|
// reaching for a sidebar wants to find the launcher and the account menu in
|
|
60
62
|
// the same breath, since the shell already mounts both.
|
|
61
63
|
var app_shell_js_1 = require("./components/app-shell.js");
|
|
62
|
-
Object.defineProperty(exports, "
|
|
64
|
+
Object.defineProperty(exports, "AppShell", { enumerable: true, get: function () { return app_shell_js_1.AppShell; } });
|
|
63
65
|
var side_nav_js_1 = require("./components/side-nav.js");
|
|
64
66
|
Object.defineProperty(exports, "ConnectSideNav", { enumerable: true, get: function () { return side_nav_js_1.ConnectSideNav; } });
|
|
65
67
|
var use_active_path_js_1 = require("./use-active-path.js");
|
|
@@ -77,6 +79,30 @@ Object.defineProperty(exports, "isNavItemActive", { enumerable: true, get: funct
|
|
|
77
79
|
Object.defineProperty(exports, "gateExemptPaths", { enumerable: true, get: function () { return nav_js_1.gateExemptPaths; } });
|
|
78
80
|
var apps_grid_js_1 = require("./components/apps-grid.js");
|
|
79
81
|
Object.defineProperty(exports, "ConnectAppsGrid", { enumerable: true, get: function () { return apps_grid_js_1.ConnectAppsGrid; } });
|
|
82
|
+
// `AppShell askAi` mounts this; exported for an app that wants the panel
|
|
83
|
+
// without the frame, or wants it somewhere other than the shell's flex row
|
|
84
|
+
// (at which point it overlays rather than pushes - see its doc comment).
|
|
85
|
+
var ask_ai_js_1 = require("./components/ask-ai.js");
|
|
86
|
+
Object.defineProperty(exports, "ConnectAskAi", { enumerable: true, get: function () { return ask_ai_js_1.ConnectAskAi; } });
|
|
87
|
+
// The seven widgets an Ask AI answer can draw, and the formatter they share -
|
|
88
|
+
// exported so a partner rendering Connect's chat somewhere else entirely (a
|
|
89
|
+
// support console, an embedded assistant) gets the same table, chart and metric
|
|
90
|
+
// tiles rather than reimplementing them from the wire types.
|
|
91
|
+
var ai_widgets_js_1 = require("./components/ai-widgets.js");
|
|
92
|
+
Object.defineProperty(exports, "AiTable", { enumerable: true, get: function () { return ai_widgets_js_1.AiTable; } });
|
|
93
|
+
Object.defineProperty(exports, "AiChart", { enumerable: true, get: function () { return ai_widgets_js_1.AiChart; } });
|
|
94
|
+
Object.defineProperty(exports, "AiMetrics", { enumerable: true, get: function () { return ai_widgets_js_1.AiMetrics; } });
|
|
95
|
+
Object.defineProperty(exports, "AiTimeline", { enumerable: true, get: function () { return ai_widgets_js_1.AiTimeline; } });
|
|
96
|
+
Object.defineProperty(exports, "AiDetails", { enumerable: true, get: function () { return ai_widgets_js_1.AiDetails; } });
|
|
97
|
+
Object.defineProperty(exports, "AiSteps", { enumerable: true, get: function () { return ai_widgets_js_1.AiSteps; } });
|
|
98
|
+
Object.defineProperty(exports, "AiSources", { enumerable: true, get: function () { return ai_widgets_js_1.AiSources; } });
|
|
99
|
+
var ai_format_js_1 = require("./components/ai-format.js");
|
|
100
|
+
Object.defineProperty(exports, "formatAiValue", { enumerable: true, get: function () { return ai_format_js_1.formatAiValue; } });
|
|
101
|
+
Object.defineProperty(exports, "isNumericAiFormat", { enumerable: true, get: function () { return ai_format_js_1.isNumericAiFormat; } });
|
|
102
|
+
Object.defineProperty(exports, "aiStatusTone", { enumerable: true, get: function () { return ai_format_js_1.aiStatusTone; } });
|
|
103
|
+
Object.defineProperty(exports, "AI_EMPTY_VALUE", { enumerable: true, get: function () { return ai_format_js_1.AI_EMPTY_VALUE; } });
|
|
104
|
+
Object.defineProperty(exports, "AI_TONE_CHIP", { enumerable: true, get: function () { return ai_format_js_1.AI_TONE_CHIP; } });
|
|
105
|
+
Object.defineProperty(exports, "AI_TONE_DOT", { enumerable: true, get: function () { return ai_format_js_1.AI_TONE_DOT; } });
|
|
80
106
|
var integrations_modal_js_1 = require("./components/integrations-modal.js");
|
|
81
107
|
Object.defineProperty(exports, "ConnectIntegrationsModal", { enumerable: true, get: function () { return integrations_modal_js_1.ConnectIntegrationsModal; } });
|
|
82
108
|
var plans_js_1 = require("./components/plans.js");
|
|
@@ -104,6 +130,8 @@ Object.defineProperty(exports, "useConnectAccessGate", { enumerable: true, get:
|
|
|
104
130
|
var feature_gate_js_1 = require("./components/feature-gate.js");
|
|
105
131
|
Object.defineProperty(exports, "ConnectFeatureGate", { enumerable: true, get: function () { return feature_gate_js_1.ConnectFeatureGate; } });
|
|
106
132
|
Object.defineProperty(exports, "useConnectFeature", { enumerable: true, get: function () { return feature_gate_js_1.useConnectFeature; } });
|
|
133
|
+
var workspace_type_gate_js_1 = require("./components/workspace-type-gate.js");
|
|
134
|
+
Object.defineProperty(exports, "ConnectWorkspaceTypeGate", { enumerable: true, get: function () { return workspace_type_gate_js_1.ConnectWorkspaceTypeGate; } });
|
|
107
135
|
var usage_meter_js_1 = require("./components/usage-meter.js");
|
|
108
136
|
Object.defineProperty(exports, "ConnectUsageMeter", { enumerable: true, get: function () { return usage_meter_js_1.ConnectUsageMeter; } });
|
|
109
137
|
var plans_page_js_1 = require("./components/plans-page.js");
|