@tutti-os/workspace-app-center 0.0.49 → 0.0.51

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/ui/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import {
2
+ isCommunityRecommendedApp,
2
3
  resolveDefaultAppFactoryProvider,
3
4
  resolveSelectedAppFactoryProvider,
5
+ sortCommunityApps,
4
6
  sortMyAppsByCreatedDesc,
5
7
  sortRecommendedApps,
6
8
  sortRecommendedAppsForAllTab
7
- } from "../chunk-DXJSCFY7.js";
9
+ } from "../chunk-5BSF53JK.js";
8
10
 
9
11
  // src/ui/AppCard.tsx
10
- import { memo, useCallback, useMemo, useRef, useState } from "react";
12
+ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
11
13
  import {
12
14
  Button,
13
15
  ChatIcon,
@@ -26,11 +28,15 @@ import {
26
28
  PopoverContent,
27
29
  PopoverTrigger,
28
30
  RefreshIcon,
31
+ Tooltip,
32
+ TooltipContent,
33
+ TooltipProvider,
34
+ TooltipTrigger,
29
35
  UninstallIcon,
30
36
  UploadIcon,
31
37
  cn
32
38
  } from "@tutti-os/ui-system";
33
- import { jsx, jsxs } from "react/jsx-runtime";
39
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
34
40
  var AppCard = memo(function AppCard2({
35
41
  actions,
36
42
  app,
@@ -52,11 +58,12 @@ var AppCard = memo(function AppCard2({
52
58
  const canOpenFromCard = app.canOpen;
53
59
  const canPublishFactoryUpdate = app.canPublishFactoryUpdate && !!app.factoryJobId;
54
60
  const canOpenFactorySession = app.canOpenFactorySession && !!app.factoryAgentSessionId && !!app.factoryJobId;
61
+ const communityDeveloper = resolveCommunityAppDeveloper(app);
55
62
  const actionContext = useMemo(
56
63
  () => createWorkspaceAppActionContext(app),
57
64
  [app]
58
65
  );
59
- const hasMoreActions = canPublishFactoryUpdate || canOpenFactorySession || app.canExport || app.canDelete || app.canReplaceIcon || app.canReloadLocal || app.canOpenFolder || app.canOpenPackageFolder || app.canUninstall;
66
+ const hasMoreActions = communityDeveloper != null || canPublishFactoryUpdate || canOpenFactorySession || app.canExport || app.canDelete || app.canReplaceIcon || app.canReloadLocal || app.canOpenFolder || app.canOpenPackageFolder || app.canUninstall;
60
67
  const executePrimaryAction = useCallback(() => {
61
68
  if (app.primaryAction === "retry") {
62
69
  void actions.retryApp?.(app.id);
@@ -128,6 +135,7 @@ var AppCard = memo(function AppCard2({
128
135
  app,
129
136
  canOpenFactorySession,
130
137
  canPublishFactoryUpdate,
138
+ communityDeveloper,
131
139
  copy
132
140
  }
133
141
  ) : null }),
@@ -166,11 +174,27 @@ var AppCard = memo(function AppCard2({
166
174
  /* @__PURE__ */ jsxs("div", { className: "mt-5 flex min-h-0 min-w-0 flex-1 flex-col p-1", children: [
167
175
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
168
176
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-baseline gap-2", children: [
169
- /* @__PURE__ */ jsx("h3", { className: "block min-w-0 truncate text-[15px] font-semibold leading-6 tracking-[0] text-[var(--text-primary)]", children: app.name }),
177
+ /* @__PURE__ */ jsx(
178
+ AppCardTextTooltip,
179
+ {
180
+ as: "h3",
181
+ className: "block min-w-0 truncate text-[15px] font-semibold leading-6 tracking-[0] text-[var(--text-primary)]",
182
+ content: app.name,
183
+ children: app.name
184
+ }
185
+ ),
170
186
  app.version ? /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-none text-[11px] leading-4 text-[var(--text-tertiary)] opacity-0 transition-opacity duration-150 group-hover:opacity-100 group-focus-within:opacity-100", children: copy.t("labels.version", { version: app.version }) }) : null,
171
187
  app.canReloadLocal ? /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-none rounded-[5px] border border-[color:var(--line-2)] px-1.5 py-0 text-[10px] font-medium leading-4 text-[var(--text-secondary)]", children: copy.t("labels.localDev") }) : null
172
188
  ] }),
173
- app.description ? /* @__PURE__ */ jsx("p", { className: "mt-2 line-clamp-3 text-[13px] font-normal leading-[1.3] text-[var(--text-secondary)]", children: app.description }) : null
189
+ app.description ? /* @__PURE__ */ jsx(
190
+ AppCardTextTooltip,
191
+ {
192
+ as: "p",
193
+ className: "mt-2 line-clamp-3 text-[13px] font-normal leading-[1.3] text-[var(--text-secondary)]",
194
+ content: app.description,
195
+ children: app.description
196
+ }
197
+ ) : null
174
198
  ] }),
175
199
  app.errorMessage ? /* @__PURE__ */ jsx("div", { className: "mt-auto flex min-w-0 flex-col gap-3 pt-3", children: /* @__PURE__ */ jsx(
176
200
  "p",
@@ -183,6 +207,7 @@ var AppCard = memo(function AppCard2({
183
207
  showDeveloperSources ? /* @__PURE__ */ jsx(
184
208
  AppDeveloperSourceRow,
185
209
  {
210
+ actions,
186
211
  app,
187
212
  copy,
188
213
  officialDeveloperIconUrl
@@ -193,6 +218,72 @@ var AppCard = memo(function AppCard2({
193
218
  }
194
219
  );
195
220
  });
221
+ function AppCardTextTooltip({
222
+ as,
223
+ children,
224
+ className,
225
+ content
226
+ }) {
227
+ const normalizedContent = content.trim();
228
+ const textRef = useRef(
229
+ null
230
+ );
231
+ const [overflowing, setOverflowing] = useState(false);
232
+ useEffect(() => {
233
+ const element = textRef.current;
234
+ if (!element || !normalizedContent) {
235
+ setOverflowing(false);
236
+ return;
237
+ }
238
+ let frame = 0;
239
+ const measure = () => {
240
+ frame = 0;
241
+ setOverflowing(
242
+ element.scrollWidth - element.clientWidth > 1 || element.scrollHeight - element.clientHeight > 1
243
+ );
244
+ };
245
+ const queueMeasure = () => {
246
+ if (frame !== 0) {
247
+ window.cancelAnimationFrame(frame);
248
+ }
249
+ frame = window.requestAnimationFrame(measure);
250
+ };
251
+ queueMeasure();
252
+ window.addEventListener("resize", queueMeasure);
253
+ if (typeof ResizeObserver === "undefined") {
254
+ return () => {
255
+ window.removeEventListener("resize", queueMeasure);
256
+ if (frame !== 0) {
257
+ window.cancelAnimationFrame(frame);
258
+ }
259
+ };
260
+ }
261
+ const observer = new ResizeObserver(queueMeasure);
262
+ observer.observe(element);
263
+ return () => {
264
+ observer.disconnect();
265
+ window.removeEventListener("resize", queueMeasure);
266
+ if (frame !== 0) {
267
+ window.cancelAnimationFrame(frame);
268
+ }
269
+ };
270
+ }, [normalizedContent]);
271
+ const textElement = as === "h3" ? /* @__PURE__ */ jsx("h3", { ref: textRef, className, children }) : /* @__PURE__ */ jsx("p", { ref: textRef, className, children });
272
+ if (!normalizedContent) {
273
+ return textElement;
274
+ }
275
+ return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
276
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: textElement }),
277
+ overflowing ? /* @__PURE__ */ jsx(
278
+ TooltipContent,
279
+ {
280
+ className: "max-w-[min(420px,calc(100vw-32px))] whitespace-normal text-left [overflow-wrap:anywhere]",
281
+ collisionPadding: 12,
282
+ children: normalizedContent
283
+ }
284
+ ) : null
285
+ ] }) });
286
+ }
196
287
  function createWorkspaceAppActionContext(app) {
197
288
  return {
198
289
  installationId: app.installationId ?? null,
@@ -201,6 +292,7 @@ function createWorkspaceAppActionContext(app) {
201
292
  };
202
293
  }
203
294
  function AppDeveloperSourceRow({
295
+ actions,
204
296
  app,
205
297
  copy,
206
298
  officialDeveloperIconUrl
@@ -313,7 +405,7 @@ function AppDeveloperSourceRow({
313
405
  type: "button",
314
406
  onClick: (event) => {
315
407
  event.stopPropagation();
316
- openExternalURL(author.url);
408
+ openExternalURL(actions, author.url);
317
409
  },
318
410
  children: [
319
411
  /* @__PURE__ */ jsx(
@@ -335,7 +427,7 @@ function AppDeveloperSourceRow({
335
427
  type: "button",
336
428
  onClick: (event) => {
337
429
  event.stopPropagation();
338
- openExternalURL(repository.url);
430
+ openExternalURL(actions, repository.url);
339
431
  },
340
432
  children: [
341
433
  /* @__PURE__ */ jsx(GitHubBrandIcon, { className: "size-4" }),
@@ -401,11 +493,15 @@ function isOfficialAuthor(name) {
401
493
  function displayRepositoryURL(url) {
402
494
  return url.replace(/^https?:\/\//u, "");
403
495
  }
404
- function openExternalURL(url) {
496
+ function openExternalURL(actions, url) {
405
497
  const target = url?.trim();
406
498
  if (!target) {
407
499
  return;
408
500
  }
501
+ if (actions.openExternalUrl) {
502
+ void actions.openExternalUrl(target);
503
+ return;
504
+ }
409
505
  window.open(target, "_blank", "noopener,noreferrer");
410
506
  }
411
507
  function AppCardMoreActions({
@@ -413,9 +509,26 @@ function AppCardMoreActions({
413
509
  app,
414
510
  canOpenFactorySession,
415
511
  canPublishFactoryUpdate,
512
+ communityDeveloper,
416
513
  copy
417
514
  }) {
418
515
  const menuItems = [];
516
+ if (communityDeveloper) {
517
+ menuItems.push({
518
+ content: /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
519
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: copy.t("sources.developer") }),
520
+ /* @__PURE__ */ jsx(AuthorAvatar, { author: communityDeveloper }),
521
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: communityDeveloper.name })
522
+ ] }),
523
+ key: "developer",
524
+ label: copy.t("sources.developerMenuLabel", {
525
+ name: communityDeveloper.name
526
+ }),
527
+ onSelect: () => {
528
+ openExternalURL(actions, communityDeveloper.url);
529
+ }
530
+ });
531
+ }
419
532
  if (canPublishFactoryUpdate) {
420
533
  menuItems.push({
421
534
  attention: true,
@@ -590,8 +703,10 @@ function AppCenterActionMenu({
590
703
  item.onSelect();
591
704
  },
592
705
  children: [
593
- item.icon,
594
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
706
+ item.content ?? /* @__PURE__ */ jsxs(Fragment, { children: [
707
+ item.icon,
708
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label })
709
+ ] }),
595
710
  item.attention ? /* @__PURE__ */ jsx(
596
711
  "i",
597
712
  {
@@ -607,6 +722,14 @@ function AppCenterActionMenu({
607
722
  )
608
723
  ] });
609
724
  }
725
+ function resolveCommunityAppDeveloper(app) {
726
+ if (!isCommunityRecommendedApp(app.id)) {
727
+ return null;
728
+ }
729
+ return app.authors?.find(
730
+ (author) => author.name.trim() && Boolean(author.url?.trim())
731
+ ) ?? null;
732
+ }
610
733
  function AppIcon({
611
734
  app,
612
735
  onReplaceIcon,
@@ -723,7 +846,7 @@ function statusClassName(status) {
723
846
  }
724
847
 
725
848
  // src/ui/AppCenterPanel.tsx
726
- import { useEffect, useId, useMemo as useMemo2, useState as useState2 } from "react";
849
+ import { useEffect as useEffect2, useId, useMemo as useMemo2, useState as useState2 } from "react";
727
850
  import {
728
851
  Badge,
729
852
  BareIconButton,
@@ -749,16 +872,16 @@ import {
749
872
  SectionTabs,
750
873
  Spinner,
751
874
  Textarea,
752
- Tooltip,
753
- TooltipContent,
754
- TooltipTrigger,
875
+ Tooltip as Tooltip2,
876
+ TooltipContent as TooltipContent2,
877
+ TooltipTrigger as TooltipTrigger2,
755
878
  ToastProvider,
756
879
  ToastRoot,
757
880
  ToastTitle,
758
881
  UploadFolderIcon,
759
882
  cn as cn2
760
883
  } from "@tutti-os/ui-system";
761
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
884
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
762
885
  var factoryTemplates = [
763
886
  {
764
887
  defaultNameKey: "factory.templates.lovart.defaultName",
@@ -865,7 +988,7 @@ function AppCenterPanel({
865
988
  const [uncontrolledActiveAppTab, setUncontrolledActiveAppTab] = useState2("recommended");
866
989
  const [activeRecommendedCategoryTab, setActiveRecommendedCategoryTab] = useState2("all");
867
990
  const activeAppTab = controlledActiveAppTab ?? uncontrolledActiveAppTab;
868
- useEffect(() => {
991
+ useEffect2(() => {
869
992
  setSelectedProvider(
870
993
  (current) => resolveSelectedAppFactoryProvider(
871
994
  current,
@@ -877,7 +1000,7 @@ function AppCenterPanel({
877
1000
  const selectedProviderOption = normalizedProviderOptions.find(
878
1001
  (option) => option.provider === selectedProvider
879
1002
  ) ?? null;
880
- useEffect(() => {
1003
+ useEffect2(() => {
881
1004
  if (!factoryDialogOpen) {
882
1005
  setProviderConfiguration(null);
883
1006
  setProviderConfigurationStatus("idle");
@@ -917,7 +1040,7 @@ function AppCenterPanel({
917
1040
  const modelOptions = providerConfiguration?.modelOptions ?? [];
918
1041
  const permissionModeOptions = providerConfiguration?.permissionModeOptions ?? [];
919
1042
  const reasoningEffortOptions = providerConfiguration?.reasoningEffortOptions ?? [];
920
- useEffect(() => {
1043
+ useEffect2(() => {
921
1044
  setSelectedModel(
922
1045
  (current) => resolveSelectedFactoryOptionValue(
923
1046
  current,
@@ -926,7 +1049,7 @@ function AppCenterPanel({
926
1049
  )
927
1050
  );
928
1051
  }, [modelOptions, providerConfiguration?.defaultModel]);
929
- useEffect(() => {
1052
+ useEffect2(() => {
930
1053
  setSelectedPermissionModeId(
931
1054
  (current) => resolveSelectedFactoryOptionValue(
932
1055
  current,
@@ -935,7 +1058,7 @@ function AppCenterPanel({
935
1058
  )
936
1059
  );
937
1060
  }, [permissionModeOptions, providerConfiguration?.defaultPermissionModeId]);
938
- useEffect(() => {
1061
+ useEffect2(() => {
939
1062
  setSelectedReasoningEffort(
940
1063
  (current) => resolveSelectedFactoryOptionValue(
941
1064
  current,
@@ -1076,12 +1199,16 @@ function AppCenterPanel({
1076
1199
  viewModel.apps.filter((app) => app.sourceKind === "local")
1077
1200
  );
1078
1201
  const recommendedSourceApps = viewModel.apps.filter(
1079
- (app) => app.sourceKind !== "local"
1202
+ (app) => app.sourceKind !== "local" && !isCommunityRecommendedApp(app.id)
1203
+ );
1204
+ const communitySourceApps = viewModel.apps.filter(
1205
+ (app) => app.sourceKind !== "local" && isCommunityRecommendedApp(app.id)
1080
1206
  );
1081
1207
  const recommendedApps = sortRecommendedApps(recommendedSourceApps);
1082
1208
  const recommendedAppsForAllTab = sortRecommendedAppsForAllTab(
1083
1209
  recommendedSourceApps
1084
1210
  );
1211
+ const communityApps = sortCommunityApps(communitySourceApps);
1085
1212
  const recommendedCategoryTabs = createRecommendedCategoryTabs(
1086
1213
  recommendedApps,
1087
1214
  copy
@@ -1092,9 +1219,9 @@ function AppCenterPanel({
1092
1219
  const activeRecommendedApps = activeRecommendedCategoryLabel == null ? recommendedAppsForAllTab : recommendedApps.filter(
1093
1220
  (app) => app.category === activeRecommendedCategoryLabel
1094
1221
  );
1095
- const activeApps = activeAppTab === "recommended" ? activeRecommendedApps : myApps;
1096
- const activeAppTabTitle = activeAppTab === "recommended" ? copy.t("labels.recommendedApps") : copy.t("labels.myApps");
1097
- const activeAppEmptyMessage = activeAppTab === "recommended" ? copy.t("messages.recommendedAppsEmpty") : copy.t("messages.myAppsEmpty");
1222
+ const activeApps = activeAppTab === "recommended" ? activeRecommendedApps : activeAppTab === "community" ? communityApps : myApps;
1223
+ const activeAppTabTitle = activeAppTab === "recommended" ? copy.t("labels.recommendedApps") : activeAppTab === "community" ? copy.t("labels.communityApps") : copy.t("labels.myApps");
1224
+ const activeAppEmptyMessage = activeAppTab === "recommended" ? copy.t("messages.recommendedAppsEmpty") : activeAppTab === "community" ? copy.t("messages.communityAppsEmpty") : copy.t("messages.myAppsEmpty");
1098
1225
  const pendingDeleteAppInstalled = pendingDeleteApp?.installed ?? false;
1099
1226
  const deleteAppConfirmLabel = copy.t(
1100
1227
  pendingDeleteAppInstalled ? "actions.uninstallAndDeleteApp" : "actions.deleteApp"
@@ -1144,6 +1271,10 @@ function AppCenterPanel({
1144
1271
  label: copy.t("labels.recommendedApps"),
1145
1272
  value: "recommended"
1146
1273
  },
1274
+ {
1275
+ label: copy.t("labels.communityApps"),
1276
+ value: "community"
1277
+ },
1147
1278
  {
1148
1279
  label: copy.t("labels.myApps"),
1149
1280
  value: "my"
@@ -1984,9 +2115,9 @@ function AppCenterHeaderActions({
1984
2115
  onImportApp,
1985
2116
  onLoadLocalApp
1986
2117
  }) {
1987
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
1988
- /* @__PURE__ */ jsxs2(Tooltip, { children: [
1989
- /* @__PURE__ */ jsx2(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs2(
2118
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
2119
+ /* @__PURE__ */ jsxs2(Tooltip2, { children: [
2120
+ /* @__PURE__ */ jsx2(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsxs2(
1990
2121
  Button2,
1991
2122
  {
1992
2123
  size: "default",
@@ -1999,10 +2130,10 @@ function AppCenterHeaderActions({
1999
2130
  ]
2000
2131
  }
2001
2132
  ) }),
2002
- /* @__PURE__ */ jsx2(TooltipContent, { side: "bottom", children: copy.t("actions.loadUnpackedAppTooltip") })
2133
+ /* @__PURE__ */ jsx2(TooltipContent2, { side: "bottom", children: copy.t("actions.loadUnpackedAppTooltip") })
2003
2134
  ] }),
2004
- /* @__PURE__ */ jsxs2(Tooltip, { children: [
2005
- /* @__PURE__ */ jsx2(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs2(
2135
+ /* @__PURE__ */ jsxs2(Tooltip2, { children: [
2136
+ /* @__PURE__ */ jsx2(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsxs2(
2006
2137
  Button2,
2007
2138
  {
2008
2139
  size: "default",
@@ -2015,7 +2146,7 @@ function AppCenterHeaderActions({
2015
2146
  ]
2016
2147
  }
2017
2148
  ) }),
2018
- /* @__PURE__ */ jsx2(TooltipContent, { side: "bottom", children: copy.t("actions.importAppTooltip") })
2149
+ /* @__PURE__ */ jsx2(TooltipContent2, { side: "bottom", children: copy.t("actions.importAppTooltip") })
2019
2150
  ] }),
2020
2151
  /* @__PURE__ */ jsxs2(
2021
2152
  Button2,
@@ -2037,8 +2168,8 @@ function AppCenterRecommendedHeaderActions({
2037
2168
  loading,
2038
2169
  onRefreshCatalog
2039
2170
  }) {
2040
- return /* @__PURE__ */ jsxs2(Tooltip, { children: [
2041
- /* @__PURE__ */ jsx2(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx2(
2171
+ return /* @__PURE__ */ jsxs2(Tooltip2, { children: [
2172
+ /* @__PURE__ */ jsx2(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsx2(
2042
2173
  Button2,
2043
2174
  {
2044
2175
  "aria-label": copy.t("actions.refreshCatalog"),
@@ -2051,7 +2182,7 @@ function AppCenterRecommendedHeaderActions({
2051
2182
  children: /* @__PURE__ */ jsx2(RefreshIcon2, { className: loading ? "animate-spin" : void 0 })
2052
2183
  }
2053
2184
  ) }),
2054
- /* @__PURE__ */ jsx2(TooltipContent, { side: "bottom", children: copy.t("actions.refreshCatalog") })
2185
+ /* @__PURE__ */ jsx2(TooltipContent2, { side: "bottom", children: copy.t("actions.refreshCatalog") })
2055
2186
  ] });
2056
2187
  }
2057
2188
  function FactoryJobStatusIndicator({