@tangle-network/sandbox-ui 0.26.0 → 0.27.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/dist/chat.d.ts +19 -3
- package/dist/chat.js +3 -3
- package/dist/{chunk-JDMX4HHN.js → chunk-6NQBODYV.js} +66 -8
- package/dist/{chunk-KANKBACI.js → chunk-BJFWPP5T.js} +139 -35
- package/dist/{chunk-HXIYUQN2.js → chunk-ELGN7DPS.js} +23 -11
- package/dist/{chunk-BP6ZTBI6.js → chunk-I3IVCGND.js} +35 -35
- package/dist/dashboard.d.ts +3 -3
- package/dist/dashboard.js +12 -4
- package/dist/globals.css +29 -15
- package/dist/{harness-picker-ppDe7ap-.d.ts → harness-picker-lyeEncN4.d.ts} +10 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -6
- package/dist/integrations.d.ts +17 -5
- package/dist/integrations.js +344 -121
- package/dist/{model-picker-DUfMTQo5.d.ts → model-picker-Cmisf9Y8.d.ts} +33 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1 -1
- package/dist/styles.css +29 -15
- package/dist/workspace.js +1 -1
- package/package.json +1 -1
package/dist/integrations.js
CHANGED
|
@@ -1,87 +1,197 @@
|
|
|
1
1
|
// src/integrations/integrations-panel.tsx
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
Badge,
|
|
5
|
-
Button,
|
|
6
|
-
Card,
|
|
7
|
-
CardContent,
|
|
8
|
-
CardHeader,
|
|
9
|
-
EmptyState
|
|
10
|
-
} from "@tangle-network/ui/primitives";
|
|
3
|
+
import { Card, CardContent, EmptyState } from "@tangle-network/ui/primitives";
|
|
11
4
|
import { cn } from "@tangle-network/ui/utils";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
5
|
+
import { Check, Search, Settings2 } from "lucide-react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
var DEFAULT_FEATURED_IDS = [
|
|
8
|
+
"gmail",
|
|
9
|
+
"google-sheets",
|
|
10
|
+
"google-drive",
|
|
11
|
+
"google-docs",
|
|
12
|
+
"google-calendar",
|
|
13
|
+
"outlook",
|
|
14
|
+
"outlook-mail",
|
|
15
|
+
"microsoft-calendar",
|
|
16
|
+
"microsoft-excel",
|
|
17
|
+
"microsoft-teams",
|
|
18
|
+
"slack",
|
|
19
|
+
"discord",
|
|
20
|
+
"hubspot",
|
|
21
|
+
"salesforce",
|
|
22
|
+
"notion",
|
|
23
|
+
"airtable",
|
|
24
|
+
"github",
|
|
25
|
+
"gitlab",
|
|
26
|
+
"linear",
|
|
27
|
+
"jira",
|
|
28
|
+
"asana",
|
|
29
|
+
"stripe",
|
|
30
|
+
"stripe-pack",
|
|
31
|
+
"twilio",
|
|
32
|
+
"twilio-sms",
|
|
33
|
+
"linkedin",
|
|
34
|
+
"zoom",
|
|
35
|
+
"shopify",
|
|
36
|
+
"mailchimp",
|
|
37
|
+
"zendesk",
|
|
38
|
+
"intercom",
|
|
39
|
+
"dropbox",
|
|
40
|
+
"webhook"
|
|
41
|
+
];
|
|
20
42
|
function defaultConnectorOf(provider) {
|
|
21
43
|
return provider.connectors?.[0]?.connectorId ?? provider.providerId;
|
|
22
44
|
}
|
|
45
|
+
function normalizeProviderId(id) {
|
|
46
|
+
return id.toLowerCase().replace(/[_\s]+/g, "-").replace(/-(business|oauth|api|app|mail|sms|pack|connector|v\d+)$/g, "");
|
|
47
|
+
}
|
|
23
48
|
var PROVIDER_LOGO_SLUGS = {
|
|
24
49
|
gmail: "gmail",
|
|
25
50
|
googlemail: "gmail",
|
|
26
51
|
google: "google",
|
|
27
52
|
"google-drive": "googledrive",
|
|
28
|
-
googledrive: "googledrive",
|
|
29
|
-
drive: "googledrive",
|
|
30
53
|
"google-calendar": "googlecalendar",
|
|
31
|
-
googlecalendar: "googlecalendar",
|
|
32
|
-
calendar: "googlecalendar",
|
|
33
54
|
"google-sheets": "googlesheets",
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
"google-docs": "googledocs",
|
|
56
|
+
"google-meet": "googlemeet",
|
|
57
|
+
"google-forms": "googleforms",
|
|
58
|
+
"google-ads": "googleads",
|
|
59
|
+
"google-analytics": "googleanalytics",
|
|
60
|
+
outlook: "microsoftoutlook",
|
|
61
|
+
"outlook-mail": "microsoftoutlook",
|
|
62
|
+
"microsoft-outlook": "microsoftoutlook",
|
|
63
|
+
"microsoft-calendar": "microsoftoutlook",
|
|
64
|
+
"microsoft-teams": "microsoftteams",
|
|
65
|
+
teams: "microsoftteams",
|
|
66
|
+
"microsoft-excel": "microsoftexcel",
|
|
67
|
+
excel: "microsoftexcel",
|
|
68
|
+
onedrive: "microsoftonedrive",
|
|
69
|
+
sharepoint: "microsoftsharepoint",
|
|
38
70
|
twitter: "x",
|
|
39
71
|
x: "x",
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
meta: "meta",
|
|
73
|
+
"stripe-pack": "stripe",
|
|
74
|
+
"twilio-sms": "twilio",
|
|
75
|
+
webhook: "webhooks",
|
|
76
|
+
webhooks: "webhooks",
|
|
45
77
|
hubspot: "hubspot",
|
|
46
78
|
salesforce: "salesforce",
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
jira: "jira",
|
|
52
|
-
asana: "asana",
|
|
53
|
-
trello: "trello",
|
|
54
|
-
dropbox: "dropbox",
|
|
55
|
-
box: "box",
|
|
56
|
-
outlook: "microsoftoutlook",
|
|
57
|
-
"microsoft-outlook": "microsoftoutlook",
|
|
58
|
-
"microsoft-teams": "microsoftteams",
|
|
59
|
-
zoom: "zoom",
|
|
60
|
-
shopify: "shopify",
|
|
61
|
-
mailchimp: "mailchimp",
|
|
62
|
-
airtable: "airtable",
|
|
79
|
+
pipedrive: "pipedrive",
|
|
80
|
+
zoho: "zoho",
|
|
81
|
+
quickbooks: "quickbooks",
|
|
82
|
+
intercom: "intercom",
|
|
63
83
|
zendesk: "zendesk",
|
|
64
|
-
|
|
84
|
+
freshdesk: "freshdesk",
|
|
85
|
+
monday: "mondaydotcom",
|
|
86
|
+
"monday-com": "mondaydotcom",
|
|
87
|
+
clickup: "clickup",
|
|
88
|
+
basecamp: "basecamp",
|
|
89
|
+
todoist: "todoist",
|
|
90
|
+
calendly: "calendly",
|
|
91
|
+
typeform: "typeform",
|
|
92
|
+
surveymonkey: "surveymonkey",
|
|
93
|
+
klaviyo: "klaviyo",
|
|
94
|
+
sendinblue: "brevo",
|
|
95
|
+
brevo: "brevo",
|
|
96
|
+
"constant-contact": "constantcontact",
|
|
97
|
+
"active-campaign": "activecampaign",
|
|
98
|
+
activecampaign: "activecampaign",
|
|
99
|
+
"google-chat": "googlechat",
|
|
100
|
+
whatsapp: "whatsapp",
|
|
101
|
+
telegram: "telegram",
|
|
102
|
+
bigquery: "googlebigquery",
|
|
103
|
+
snowflake: "snowflake",
|
|
104
|
+
postgres: "postgresql",
|
|
105
|
+
postgresql: "postgresql",
|
|
106
|
+
mysql: "mysql",
|
|
107
|
+
mongodb: "mongodb",
|
|
108
|
+
redis: "redis",
|
|
109
|
+
supabase: "supabase",
|
|
110
|
+
firebase: "firebase",
|
|
111
|
+
"aws-s3": "amazons3",
|
|
112
|
+
s3: "amazons3",
|
|
113
|
+
woocommerce: "woocommerce",
|
|
114
|
+
bigcommerce: "bigcommerce",
|
|
115
|
+
squarespace: "squarespace",
|
|
116
|
+
wix: "wix",
|
|
117
|
+
webflow: "webflow",
|
|
118
|
+
wordpress: "wordpress",
|
|
119
|
+
contentful: "contentful",
|
|
120
|
+
sanity: "sanity",
|
|
121
|
+
figma: "figma",
|
|
122
|
+
miro: "miro",
|
|
123
|
+
confluence: "confluence",
|
|
124
|
+
bitbucket: "bitbucket",
|
|
125
|
+
pagerduty: "pagerduty",
|
|
126
|
+
datadog: "datadog",
|
|
127
|
+
sentry: "sentry",
|
|
128
|
+
segment: "segment",
|
|
129
|
+
amplitude: "amplitude",
|
|
130
|
+
mixpanel: "mixpanel",
|
|
131
|
+
posthog: "posthog",
|
|
132
|
+
facebook: "facebook",
|
|
133
|
+
instagram: "instagram",
|
|
134
|
+
tiktok: "tiktok",
|
|
135
|
+
youtube: "youtube",
|
|
136
|
+
reddit: "reddit",
|
|
137
|
+
pinterest: "pinterest",
|
|
138
|
+
buffer: "buffer",
|
|
139
|
+
hootsuite: "hootsuite"
|
|
65
140
|
};
|
|
66
|
-
function
|
|
67
|
-
|
|
141
|
+
function logoCandidates(provider) {
|
|
142
|
+
const out = [];
|
|
143
|
+
if (provider.iconUrl) out.push(provider.iconUrl);
|
|
144
|
+
const raw = provider.providerId.toLowerCase();
|
|
145
|
+
const norm = normalizeProviderId(raw);
|
|
146
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
147
|
+
const curated = PROVIDER_LOGO_SLUGS[raw] ?? PROVIDER_LOGO_SLUGS[norm];
|
|
148
|
+
if (curated) slugs.add(curated);
|
|
149
|
+
slugs.add(norm.replace(/-/g, ""));
|
|
150
|
+
slugs.add(raw.replace(/[-_\s]/g, ""));
|
|
151
|
+
for (const slug of slugs) {
|
|
152
|
+
if (slug) out.push(`https://cdn.simpleicons.org/${slug}`);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
68
155
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
156
|
+
function monogramColor(seed) {
|
|
157
|
+
const palette = [
|
|
158
|
+
"#6366f1",
|
|
159
|
+
"#8b5cf6",
|
|
160
|
+
"#ec4899",
|
|
161
|
+
"#f43f5e",
|
|
162
|
+
"#f97316",
|
|
163
|
+
"#eab308",
|
|
164
|
+
"#22c55e",
|
|
165
|
+
"#14b8a6",
|
|
166
|
+
"#0ea5e9",
|
|
167
|
+
"#3b82f6"
|
|
168
|
+
];
|
|
169
|
+
let hash = 0;
|
|
170
|
+
for (let i = 0; i < seed.length; i += 1) {
|
|
171
|
+
hash = hash * 31 + seed.charCodeAt(i) >>> 0;
|
|
172
|
+
}
|
|
173
|
+
return palette[hash % palette.length];
|
|
74
174
|
}
|
|
75
|
-
function ProviderLogo({
|
|
76
|
-
|
|
77
|
-
|
|
175
|
+
function ProviderLogo({
|
|
176
|
+
provider,
|
|
177
|
+
size = 56
|
|
178
|
+
}) {
|
|
179
|
+
const candidates = React.useMemo(() => logoCandidates(provider), [provider]);
|
|
180
|
+
const [index, setIndex] = React.useState(0);
|
|
78
181
|
const label = (provider.displayName ?? provider.providerId).trim();
|
|
79
182
|
const initial = label.charAt(0).toUpperCase() || "?";
|
|
80
|
-
|
|
183
|
+
const src = candidates[index];
|
|
184
|
+
if (!src) {
|
|
81
185
|
return /* @__PURE__ */ jsx(
|
|
82
186
|
"span",
|
|
83
187
|
{
|
|
84
|
-
className: "flex
|
|
188
|
+
className: "flex shrink-0 items-center justify-center rounded-2xl font-semibold text-white",
|
|
189
|
+
style: {
|
|
190
|
+
width: size,
|
|
191
|
+
height: size,
|
|
192
|
+
backgroundColor: monogramColor(provider.providerId),
|
|
193
|
+
fontSize: Math.round(size * 0.42)
|
|
194
|
+
},
|
|
85
195
|
"aria-hidden": true,
|
|
86
196
|
children: initial
|
|
87
197
|
}
|
|
@@ -90,13 +200,14 @@ function ProviderLogo({ provider }) {
|
|
|
90
200
|
return /* @__PURE__ */ jsx(
|
|
91
201
|
"img",
|
|
92
202
|
{
|
|
93
|
-
src
|
|
203
|
+
src,
|
|
94
204
|
alt: "",
|
|
95
|
-
width:
|
|
96
|
-
height:
|
|
205
|
+
width: size,
|
|
206
|
+
height: size,
|
|
97
207
|
loading: "lazy",
|
|
98
|
-
className: "
|
|
99
|
-
|
|
208
|
+
className: "shrink-0 object-contain",
|
|
209
|
+
style: { width: size, height: size },
|
|
210
|
+
onError: () => setIndex((i) => i + 1)
|
|
100
211
|
}
|
|
101
212
|
);
|
|
102
213
|
}
|
|
@@ -108,6 +219,29 @@ function buildConnectionIndex(connections) {
|
|
|
108
219
|
}
|
|
109
220
|
return index;
|
|
110
221
|
}
|
|
222
|
+
function makeFeaturedRank(featuredIds) {
|
|
223
|
+
const rank = /* @__PURE__ */ new Map();
|
|
224
|
+
featuredIds.forEach((id, i) => {
|
|
225
|
+
const norm = normalizeProviderId(id);
|
|
226
|
+
if (!rank.has(id)) rank.set(id, i);
|
|
227
|
+
if (!rank.has(norm)) rank.set(norm, i);
|
|
228
|
+
});
|
|
229
|
+
return rank;
|
|
230
|
+
}
|
|
231
|
+
function rankOf(provider, rank) {
|
|
232
|
+
const raw = provider.providerId.toLowerCase();
|
|
233
|
+
const direct = rank.get(raw);
|
|
234
|
+
if (direct !== void 0) return direct;
|
|
235
|
+
const norm = rank.get(normalizeProviderId(raw));
|
|
236
|
+
return norm ?? Number.MAX_SAFE_INTEGER;
|
|
237
|
+
}
|
|
238
|
+
function displayNameOf(provider) {
|
|
239
|
+
return provider.displayName ?? provider.providerId.replace(/[-_]/g, " ");
|
|
240
|
+
}
|
|
241
|
+
function matchesQuery(provider, q) {
|
|
242
|
+
const hay = `${provider.displayName ?? ""} ${provider.providerId} ${provider.description ?? ""}`.toLowerCase();
|
|
243
|
+
return hay.includes(q);
|
|
244
|
+
}
|
|
111
245
|
function IntegrationsPanel({
|
|
112
246
|
catalog,
|
|
113
247
|
connections,
|
|
@@ -117,12 +251,34 @@ function IntegrationsPanel({
|
|
|
117
251
|
onConnect,
|
|
118
252
|
onDisconnect,
|
|
119
253
|
emptyCatalogLabel = "No integrations available yet.",
|
|
254
|
+
featuredIds = DEFAULT_FEATURED_IDS,
|
|
255
|
+
defaultSort = "featured",
|
|
120
256
|
className
|
|
121
257
|
}) {
|
|
258
|
+
const [query, setQuery] = React.useState("");
|
|
259
|
+
const [sort, setSort] = React.useState(defaultSort);
|
|
122
260
|
const connectionIndex = React.useMemo(
|
|
123
261
|
() => buildConnectionIndex(connections),
|
|
124
262
|
[connections]
|
|
125
263
|
);
|
|
264
|
+
const featuredRank = React.useMemo(
|
|
265
|
+
() => makeFeaturedRank(featuredIds),
|
|
266
|
+
[featuredIds]
|
|
267
|
+
);
|
|
268
|
+
const visible = React.useMemo(() => {
|
|
269
|
+
const q = query.trim().toLowerCase();
|
|
270
|
+
const filtered = q ? catalog.filter((p) => matchesQuery(p, q)) : catalog;
|
|
271
|
+
const sorted = [...filtered];
|
|
272
|
+
sorted.sort((a, b) => {
|
|
273
|
+
if (sort === "featured") {
|
|
274
|
+
const ra = rankOf(a, featuredRank);
|
|
275
|
+
const rb = rankOf(b, featuredRank);
|
|
276
|
+
if (ra !== rb) return ra - rb;
|
|
277
|
+
}
|
|
278
|
+
return displayNameOf(a).localeCompare(displayNameOf(b));
|
|
279
|
+
});
|
|
280
|
+
return sorted;
|
|
281
|
+
}, [catalog, query, sort, featuredRank]);
|
|
126
282
|
if (error) {
|
|
127
283
|
return /* @__PURE__ */ jsx(Card, { className: cn("border-destructive/50", className), children: /* @__PURE__ */ jsx(CardContent, { className: "py-6", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
|
|
128
284
|
"Failed to load integrations: ",
|
|
@@ -130,10 +286,22 @@ function IntegrationsPanel({
|
|
|
130
286
|
] }) }) });
|
|
131
287
|
}
|
|
132
288
|
if (isLoading && catalog.length === 0) {
|
|
133
|
-
return /* @__PURE__ */ jsx(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
289
|
+
return /* @__PURE__ */ jsx(
|
|
290
|
+
"div",
|
|
291
|
+
{
|
|
292
|
+
className: cn(
|
|
293
|
+
"grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6",
|
|
294
|
+
className
|
|
295
|
+
),
|
|
296
|
+
children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
297
|
+
"div",
|
|
298
|
+
{
|
|
299
|
+
className: "aspect-square animate-pulse rounded-xl border border-border bg-muted/40"
|
|
300
|
+
},
|
|
301
|
+
i
|
|
302
|
+
))
|
|
303
|
+
}
|
|
304
|
+
);
|
|
137
305
|
}
|
|
138
306
|
if (catalog.length === 0) {
|
|
139
307
|
return /* @__PURE__ */ jsx(
|
|
@@ -145,61 +313,116 @@ function IntegrationsPanel({
|
|
|
145
313
|
}
|
|
146
314
|
);
|
|
147
315
|
}
|
|
148
|
-
return /* @__PURE__ */
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
/* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-between gap-2", children: live ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
177
|
-
/* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: live.account?.displayName ?? live.account?.identity ?? "Connected" }),
|
|
178
|
-
/* @__PURE__ */ jsx(
|
|
179
|
-
Button,
|
|
180
|
-
{
|
|
181
|
-
size: "sm",
|
|
182
|
-
variant: "outline",
|
|
183
|
-
onClick: () => onDisconnect(live.id),
|
|
184
|
-
"data-testid": `disconnect-${provider.providerId}`,
|
|
185
|
-
children: "Disconnect"
|
|
186
|
-
}
|
|
187
|
-
)
|
|
188
|
-
] }) : /* @__PURE__ */ jsx(
|
|
189
|
-
Button,
|
|
316
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
|
|
317
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
318
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-2 rounded-lg border border-border bg-card px-3 py-2", children: [
|
|
319
|
+
/* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
320
|
+
/* @__PURE__ */ jsx(
|
|
321
|
+
"input",
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
value: query,
|
|
325
|
+
onChange: (e) => setQuery(e.target.value),
|
|
326
|
+
placeholder: "Search integrations...",
|
|
327
|
+
autoFocus: true,
|
|
328
|
+
"data-testid": "integration-search",
|
|
329
|
+
className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
] }),
|
|
333
|
+
/* @__PURE__ */ jsx(
|
|
334
|
+
"div",
|
|
335
|
+
{
|
|
336
|
+
role: "tablist",
|
|
337
|
+
"aria-label": "Sort integrations",
|
|
338
|
+
className: "flex shrink-0 items-center gap-1 rounded-lg border border-border bg-card p-1",
|
|
339
|
+
children: [
|
|
340
|
+
["featured", "Featured"],
|
|
341
|
+
["alpha", "A\u2013Z"]
|
|
342
|
+
].map(([value, label]) => /* @__PURE__ */ jsx(
|
|
343
|
+
"button",
|
|
190
344
|
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
345
|
+
type: "button",
|
|
346
|
+
role: "tab",
|
|
347
|
+
"aria-selected": sort === value,
|
|
348
|
+
onClick: () => setSort(value),
|
|
349
|
+
"data-testid": `sort-${value}`,
|
|
350
|
+
className: cn(
|
|
351
|
+
"rounded-md px-2.5 py-1 text-xs font-medium transition-colors",
|
|
352
|
+
sort === value ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground"
|
|
353
|
+
),
|
|
354
|
+
children: label
|
|
355
|
+
},
|
|
356
|
+
value
|
|
357
|
+
))
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
] }),
|
|
361
|
+
visible.length === 0 ? /* @__PURE__ */ jsx(
|
|
362
|
+
EmptyState,
|
|
363
|
+
{
|
|
364
|
+
title: "No matches",
|
|
365
|
+
description: `No integrations match "${query.trim()}".`
|
|
366
|
+
}
|
|
367
|
+
) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6", children: visible.map((provider) => {
|
|
368
|
+
const connectorId = defaultConnectorOf(provider);
|
|
369
|
+
const live = connectionIndex.get(
|
|
370
|
+
`${provider.providerId}:${connectorId}`
|
|
371
|
+
);
|
|
372
|
+
const name = displayNameOf(provider);
|
|
373
|
+
const connected = Boolean(live);
|
|
374
|
+
if (connected && live) {
|
|
375
|
+
return /* @__PURE__ */ jsxs(
|
|
376
|
+
"div",
|
|
377
|
+
{
|
|
378
|
+
"data-testid": `integration-${provider.providerId}`,
|
|
379
|
+
"data-connected": "true",
|
|
380
|
+
className: cn(
|
|
381
|
+
"group relative flex aspect-square flex-col items-center justify-center gap-2 rounded-xl border p-3 text-center",
|
|
382
|
+
"border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]"
|
|
383
|
+
),
|
|
384
|
+
children: [
|
|
385
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-2 top-2 flex h-4 w-4 items-center justify-center rounded-full bg-[var(--surface-success-text)] text-white", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3", strokeWidth: 3 }) }),
|
|
386
|
+
/* @__PURE__ */ jsx(
|
|
387
|
+
"button",
|
|
388
|
+
{
|
|
389
|
+
type: "button",
|
|
390
|
+
onClick: () => onDisconnect(live.id),
|
|
391
|
+
"data-testid": `manage-${provider.providerId}`,
|
|
392
|
+
"aria-label": `Manage ${name}`,
|
|
393
|
+
title: "Manage connection",
|
|
394
|
+
className: "absolute right-1.5 top-1.5 flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-background hover:text-foreground group-hover:opacity-100",
|
|
395
|
+
children: /* @__PURE__ */ jsx(Settings2, { className: "h-3.5 w-3.5" })
|
|
396
|
+
}
|
|
397
|
+
),
|
|
398
|
+
/* @__PURE__ */ jsx(ProviderLogo, { provider, size: 48 }),
|
|
399
|
+
/* @__PURE__ */ jsx("span", { className: "line-clamp-2 w-full text-xs font-medium text-foreground", children: name })
|
|
400
|
+
]
|
|
401
|
+
},
|
|
402
|
+
`${provider.providerId}:${connectorId}`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return /* @__PURE__ */ jsxs(
|
|
406
|
+
"button",
|
|
407
|
+
{
|
|
408
|
+
type: "button",
|
|
409
|
+
"data-testid": `integration-${provider.providerId}`,
|
|
410
|
+
"data-connected": "false",
|
|
411
|
+
onClick: () => onConnect({ providerId: provider.providerId, connectorId }),
|
|
412
|
+
title: provider.description ? provider.description : `Connect ${name}`,
|
|
413
|
+
className: cn(
|
|
414
|
+
"group flex aspect-square flex-col items-center justify-center gap-2 rounded-xl border border-border bg-card p-3 text-center transition-all",
|
|
415
|
+
"hover:border-primary/40 hover:bg-accent/40 hover:shadow-sm focus:outline-none focus-visible:border-primary/50 focus-visible:ring-2 focus-visible:ring-primary/20"
|
|
416
|
+
),
|
|
417
|
+
children: [
|
|
418
|
+
/* @__PURE__ */ jsx(ProviderLogo, { provider, size: 56 }),
|
|
419
|
+
/* @__PURE__ */ jsx("span", { className: "line-clamp-2 w-full text-xs font-medium text-foreground", children: name })
|
|
420
|
+
]
|
|
421
|
+
},
|
|
422
|
+
`${provider.providerId}:${connectorId}`
|
|
423
|
+
);
|
|
424
|
+
}) })
|
|
425
|
+
] });
|
|
203
426
|
}
|
|
204
427
|
|
|
205
428
|
// src/integrations/use-integrations.ts
|
|
@@ -40,6 +40,13 @@ interface ModelInfo {
|
|
|
40
40
|
hostUrl?: string;
|
|
41
41
|
labUrl?: string;
|
|
42
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Marks a model as recommended. When any catalog row carries this flag the
|
|
45
|
+
* picker surfaces those rows in a "Recommended" section at the top. When no
|
|
46
|
+
* row is flagged the picker falls back to {@link DEFAULT_FEATURED_MODEL_IDS}
|
|
47
|
+
* (matched by dedup key) so the section is never empty for a router catalog.
|
|
48
|
+
*/
|
|
49
|
+
featured?: boolean;
|
|
43
50
|
}
|
|
44
51
|
type ModelBrandKey = "ai21" | "alibaba" | "anthropic" | "azure" | "bedrock" | "cartesia" | "cerebras" | "cohere" | "deepseek" | "elevenlabs" | "fal" | "fireworks" | "google" | "groq" | "kuaishou" | "luma" | "meta" | "mistral" | "moonshot" | "openai" | "openrouter" | "perplexity" | "pika" | "replicate" | "runway" | "stability" | "tangle" | "tcloud" | "together" | "vertex" | "xai" | "zai" | "unknown";
|
|
45
52
|
type ModelPickerVariant = "field" | "pill";
|
|
@@ -85,10 +92,35 @@ interface ModelPickerProps {
|
|
|
85
92
|
* unless the id is already prefixed.
|
|
86
93
|
*/
|
|
87
94
|
declare function canonicalModelId(model: ModelInfo): string;
|
|
95
|
+
/**
|
|
96
|
+
* Stable key used to collapse catalog duplicates. The Tangle Router lists the
|
|
97
|
+
* same underlying model under several host prefixes (e.g. `openai/gpt-5.4`,
|
|
98
|
+
* `tcloud/gpt-5.4`, `openrouter/openai/gpt-5.4`); they all name one model and
|
|
99
|
+
* should occupy a single row. The key is `<lab>/<model-id>`: the authoring lab
|
|
100
|
+
* (inferred via {@link resolveModelBrandIdentity}, which already maps
|
|
101
|
+
* `gpt-5.4` → openai, `kimi-k2` → moonshot, etc.) plus the final id segment,
|
|
102
|
+
* so a model collapses to one identity regardless of which host serves it.
|
|
103
|
+
*/
|
|
104
|
+
declare function modelDedupKey(model: ModelInfo): string;
|
|
105
|
+
/**
|
|
106
|
+
* Collapse catalog duplicates to one row per {@link modelDedupKey}. When a
|
|
107
|
+
* model is served under multiple hosts the preferred row wins: a `featured`
|
|
108
|
+
* row beats a non-featured one, otherwise the first occurrence is kept (so
|
|
109
|
+
* caller-controlled catalog order still decides ties). Input order is
|
|
110
|
+
* preserved for the surviving rows.
|
|
111
|
+
*/
|
|
112
|
+
declare function dedupeModels(models: ReadonlyArray<ModelInfo>): ModelInfo[];
|
|
113
|
+
/**
|
|
114
|
+
* Fallback "Recommended" seed — latest frontier models per major lab, matched
|
|
115
|
+
* by {@link modelDedupKey} against the loaded catalog. Used only when no
|
|
116
|
+
* catalog row carries an explicit `featured` flag, so a standard router
|
|
117
|
+
* catalog still gets a curated top section without per-deployment config.
|
|
118
|
+
*/
|
|
119
|
+
declare const DEFAULT_FEATURED_MODEL_IDS: ReadonlyArray<string>;
|
|
88
120
|
/** Format $/M tokens. Returns null if pricing is missing or zero. */
|
|
89
121
|
declare function formatPricing(pricing: ModelInfo["pricing"]): string | null;
|
|
90
122
|
/** Format context length compactly (e.g. 200_000 → "200k"). */
|
|
91
123
|
declare function formatContext(ctx: number | undefined): string | null;
|
|
92
124
|
declare function ModelPicker({ value, onChange, models, loading, recents, popular, excludeProviders, modalities, variant, label, placeholder, className, triggerClassName, disabled, }: ModelPickerProps): react_jsx_runtime.JSX.Element;
|
|
93
125
|
|
|
94
|
-
export { type ModelInfo as M, ModelPicker as a, type ModelPickerProps as b, type ModelPickerVariant as c, canonicalModelId as d,
|
|
126
|
+
export { DEFAULT_FEATURED_MODEL_IDS as D, type ModelInfo as M, ModelPicker as a, type ModelPickerProps as b, type ModelPickerVariant as c, canonicalModelId as d, dedupeModels as e, formatContext as f, formatPricing as g, modelDedupKey as m };
|
package/dist/pages.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { CSSProperties, ReactNode } from 'react';
|
|
4
4
|
import { c as BillingSubscription, B as BillingBalance, d as BillingUsage, j as UsageDataPoint, f as PricingTier, g as TemplateCardData } from './template-card-UhV3pmRC.js';
|
|
5
|
-
export { M as ModelInfo } from './model-picker-
|
|
5
|
+
export { M as ModelInfo } from './model-picker-Cmisf9Y8.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Shared, self-contained sign-in / sign-up page for every Tangle vertical app.
|