@tutti-os/workspace-app-center 0.0.43 → 0.0.44

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
@@ -7,7 +7,7 @@ import {
7
7
  } from "../chunk-DXJSCFY7.js";
8
8
 
9
9
  // src/ui/AppCard.tsx
10
- import { memo, useCallback, useMemo } from "react";
10
+ import { memo, useCallback, useMemo, useRef, useState } from "react";
11
11
  import {
12
12
  Button,
13
13
  ChatIcon,
@@ -19,8 +19,12 @@ import {
19
19
  DropdownMenuItem,
20
20
  DropdownMenuTrigger,
21
21
  FolderIcon,
22
+ GitHubBrandIcon,
22
23
  MoreHorizontalIcon,
23
24
  NavApplicationsFilledIcon,
25
+ Popover,
26
+ PopoverContent,
27
+ PopoverTrigger,
24
28
  RefreshIcon,
25
29
  UninstallIcon,
26
30
  UploadIcon,
@@ -31,7 +35,9 @@ var AppCard = memo(function AppCard2({
31
35
  actions,
32
36
  app,
33
37
  className,
34
- copy
38
+ copy,
39
+ officialDeveloperIconUrl = null,
40
+ showDeveloperSources = false
35
41
  }) {
36
42
  const statusLabel = copy.t(app.statusLabelKey);
37
43
  const installBusy = app.installProgress != null || app.status === "installing";
@@ -173,7 +179,15 @@ var AppCard = memo(function AppCard2({
173
179
  title: app.errorMessage,
174
180
  children: copy.t("messages.appRuntimeFailed")
175
181
  }
176
- ) }) : null
182
+ ) }) : null,
183
+ showDeveloperSources ? /* @__PURE__ */ jsx(
184
+ AppDeveloperSourceRow,
185
+ {
186
+ app,
187
+ copy,
188
+ officialDeveloperIconUrl
189
+ }
190
+ ) : null
177
191
  ] })
178
192
  ]
179
193
  }
@@ -186,6 +200,214 @@ function createWorkspaceAppActionContext(app) {
186
200
  launchUrl: app.launchUrl ?? null
187
201
  };
188
202
  }
203
+ function AppDeveloperSourceRow({
204
+ app,
205
+ copy,
206
+ officialDeveloperIconUrl
207
+ }) {
208
+ const sourceAuthors = app.authors ?? [];
209
+ const repository = app.repository ?? null;
210
+ const authors = sourceAuthors.length > 0 ? sourceAuthors : app.sourceKind === "bundled" ? [{ name: "Tutti" }] : [];
211
+ const [popoverOpen, setPopoverOpen] = useState(false);
212
+ const closeTimerRef = useRef(null);
213
+ const clearCloseTimer = useCallback(() => {
214
+ if (closeTimerRef.current === null) {
215
+ return;
216
+ }
217
+ window.clearTimeout(closeTimerRef.current);
218
+ closeTimerRef.current = null;
219
+ }, []);
220
+ const openPopover = useCallback(() => {
221
+ clearCloseTimer();
222
+ setPopoverOpen(true);
223
+ }, [clearCloseTimer]);
224
+ const scheduleClosePopover = useCallback(() => {
225
+ clearCloseTimer();
226
+ closeTimerRef.current = window.setTimeout(() => {
227
+ setPopoverOpen(false);
228
+ closeTimerRef.current = null;
229
+ }, 120);
230
+ }, [clearCloseTimer]);
231
+ if (authors.length === 0 && repository === null) {
232
+ return null;
233
+ }
234
+ const primaryAuthor = authors[0] ?? null;
235
+ const rowLabel = authors.length > 1 ? copy.t("sources.developerCount", { count: String(authors.length) }) : primaryAuthor?.name || (repository ? copy.t("sources.githubRepository") : "");
236
+ const official = isOfficialAuthor(primaryAuthor?.name);
237
+ const canOpenPopover = repository !== null || authors.some((author) => Boolean(author.url));
238
+ const content = /* @__PURE__ */ jsx("div", { className: "mt-auto min-w-0 pt-3", children: /* @__PURE__ */ jsxs("div", { className: "group/source flex min-h-7 min-w-0 items-center gap-2 border-t border-[color:var(--line-2)] pt-2 text-[12px] leading-4 text-[var(--text-secondary)]", children: [
239
+ /* @__PURE__ */ jsx(
240
+ AvatarStack,
241
+ {
242
+ authors,
243
+ fallbackIconUrl: official ? officialDeveloperIconUrl : null
244
+ }
245
+ ),
246
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: rowLabel }),
247
+ official ? /* @__PURE__ */ jsx("span", { className: "shrink-0 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("sources.official") }) : null,
248
+ canOpenPopover ? /* @__PURE__ */ jsxs("span", { className: "flex size-5 shrink-0 items-center justify-center text-[var(--text-secondary)]", children: [
249
+ /* @__PURE__ */ jsx(GitHubBrandIcon, { className: "size-4 group-hover/source:hidden" }),
250
+ /* @__PURE__ */ jsx(
251
+ "span",
252
+ {
253
+ "aria-hidden": "true",
254
+ className: "hidden text-[18px] leading-none group-hover/source:block",
255
+ children: "\u203A"
256
+ }
257
+ )
258
+ ] }) : null
259
+ ] }) });
260
+ if (!canOpenPopover) {
261
+ return content;
262
+ }
263
+ return /* @__PURE__ */ jsxs(Popover, { open: popoverOpen, onOpenChange: setPopoverOpen, children: [
264
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
265
+ "button",
266
+ {
267
+ className: "block w-full min-w-0 border-0 bg-transparent p-0 text-left",
268
+ type: "button",
269
+ onClick: (event) => {
270
+ event.stopPropagation();
271
+ },
272
+ onPointerEnter: openPopover,
273
+ onPointerLeave: scheduleClosePopover,
274
+ onPointerDown: (event) => {
275
+ event.stopPropagation();
276
+ },
277
+ children: content
278
+ }
279
+ ) }),
280
+ /* @__PURE__ */ jsxs(
281
+ PopoverContent,
282
+ {
283
+ align: "start",
284
+ className: "w-[280px] max-w-[min(280px,calc(100vw-32px))]",
285
+ collisionPadding: 12,
286
+ side: "bottom",
287
+ style: { zIndex: "var(--z-panel-popover)" },
288
+ onPointerEnter: openPopover,
289
+ onPointerLeave: scheduleClosePopover,
290
+ onCloseAutoFocus: (event) => {
291
+ event.preventDefault();
292
+ },
293
+ onClick: (event) => {
294
+ event.stopPropagation();
295
+ },
296
+ onOpenAutoFocus: (event) => {
297
+ event.preventDefault();
298
+ },
299
+ onPointerDown: (event) => {
300
+ event.stopPropagation();
301
+ },
302
+ onKeyDown: (event) => {
303
+ event.stopPropagation();
304
+ },
305
+ children: [
306
+ /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-[12px] font-semibold leading-4 text-[var(--text-primary)]", children: copy.t("sources.title") }),
307
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[2px]", children: [
308
+ authors.map((author, index) => /* @__PURE__ */ jsxs(
309
+ "button",
310
+ {
311
+ className: "flex min-h-8 min-w-0 items-center gap-2 rounded-[6px] px-2 py-1 text-left text-[12px] font-normal leading-4 text-[var(--text-primary)] hover:bg-[var(--transparency-hover)] disabled:cursor-default disabled:opacity-60",
312
+ disabled: !author.url,
313
+ type: "button",
314
+ onClick: (event) => {
315
+ event.stopPropagation();
316
+ openExternalURL(author.url);
317
+ },
318
+ children: [
319
+ /* @__PURE__ */ jsx(
320
+ AuthorAvatar,
321
+ {
322
+ author,
323
+ fallbackIconUrl: index === 0 && isOfficialAuthor(author.name) ? officialDeveloperIconUrl : null
324
+ }
325
+ ),
326
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: author.name })
327
+ ]
328
+ },
329
+ `${author.name}:${author.url ?? ""}`
330
+ )),
331
+ repository ? /* @__PURE__ */ jsxs(
332
+ "button",
333
+ {
334
+ className: "flex min-h-8 min-w-0 items-center gap-2 rounded-[6px] px-2 py-1 text-left text-[12px] font-normal leading-4 text-[var(--text-primary)] hover:bg-[var(--transparency-hover)]",
335
+ type: "button",
336
+ onClick: (event) => {
337
+ event.stopPropagation();
338
+ openExternalURL(repository.url);
339
+ },
340
+ children: [
341
+ /* @__PURE__ */ jsx(GitHubBrandIcon, { className: "size-4" }),
342
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: displayRepositoryURL(repository.url) })
343
+ ]
344
+ }
345
+ ) : null
346
+ ] })
347
+ ]
348
+ }
349
+ )
350
+ ] });
351
+ }
352
+ function AvatarStack({
353
+ authors,
354
+ fallbackIconUrl
355
+ }) {
356
+ const visibleAuthors = authors.slice(0, 2);
357
+ if (visibleAuthors.length === 0) {
358
+ return /* @__PURE__ */ jsx("span", { className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx(GitHubBrandIcon, { className: "size-3.5" }) });
359
+ }
360
+ return /* @__PURE__ */ jsx("span", { className: "flex shrink-0 -space-x-1", children: visibleAuthors.map((author, index) => /* @__PURE__ */ jsx(
361
+ AuthorAvatar,
362
+ {
363
+ author,
364
+ fallbackIconUrl: index === 0 ? fallbackIconUrl : null
365
+ },
366
+ `${author.name}:${author.url ?? ""}`
367
+ )) });
368
+ }
369
+ function AuthorAvatar({
370
+ author,
371
+ fallbackIconUrl
372
+ }) {
373
+ if (author.avatarUrl) {
374
+ return /* @__PURE__ */ jsx(
375
+ "img",
376
+ {
377
+ alt: "",
378
+ className: "size-5 shrink-0 rounded-full border border-[var(--background-fronted)] object-cover",
379
+ draggable: false,
380
+ src: author.avatarUrl
381
+ }
382
+ );
383
+ }
384
+ if (fallbackIconUrl) {
385
+ return /* @__PURE__ */ jsx(
386
+ "img",
387
+ {
388
+ alt: "",
389
+ className: "size-5 shrink-0 rounded-[5px] border border-[var(--background-fronted)] object-contain",
390
+ draggable: false,
391
+ src: fallbackIconUrl
392
+ }
393
+ );
394
+ }
395
+ return /* @__PURE__ */ jsx("span", { className: "flex size-5 shrink-0 items-center justify-center rounded-full border border-[var(--background-fronted)] bg-[var(--transparency-block)] text-[10px] font-semibold uppercase text-[var(--text-secondary)]", children: author.name.slice(0, 1) });
396
+ }
397
+ function isOfficialAuthor(name) {
398
+ const normalized = name?.trim().toLowerCase() ?? "";
399
+ return normalized === "tutti" || normalized === "tutti official";
400
+ }
401
+ function displayRepositoryURL(url) {
402
+ return url.replace(/^https?:\/\//u, "");
403
+ }
404
+ function openExternalURL(url) {
405
+ const target = url?.trim();
406
+ if (!target) {
407
+ return;
408
+ }
409
+ window.open(target, "_blank", "noopener,noreferrer");
410
+ }
189
411
  function AppCardMoreActions({
190
412
  actions,
191
413
  app,
@@ -501,7 +723,7 @@ function statusClassName(status) {
501
723
  }
502
724
 
503
725
  // src/ui/AppCenterPanel.tsx
504
- import { useEffect, useId, useMemo as useMemo2, useState } from "react";
726
+ import { useEffect, useId, useMemo as useMemo2, useState as useState2 } from "react";
505
727
  import {
506
728
  Badge,
507
729
  BareIconButton,
@@ -592,29 +814,31 @@ function AppCenterPanel({
592
814
  errorMessage,
593
815
  loadProviderConfiguration,
594
816
  onActiveAppTabChange,
817
+ officialDeveloperIconUrl = null,
595
818
  providerErrorMessage = null,
596
819
  providerLoading = false,
597
820
  providerOptions = [],
821
+ showDeveloperSources = false,
598
822
  viewModel
599
823
  }) {
600
824
  const promptTextareaId = useId();
601
- const [factoryDialogOpen, setFactoryDialogOpen] = useState(false);
602
- const [deleteAppBusy, setDeleteAppBusy] = useState(false);
603
- const [pendingDeleteApp, setPendingDeleteApp] = useState(null);
604
- const [uninstallAppBusy, setUninstallAppBusy] = useState(false);
605
- const [pendingUninstallApp, setPendingUninstallApp] = useState(null);
606
- const [pendingLocalRepairRequest, setPendingLocalRepairRequest] = useState(null);
607
- const [updateAppBusy, setUpdateAppBusy] = useState(false);
608
- const [localRepairBusy, setLocalRepairBusy] = useState(false);
609
- const [pendingUpdateApp, setPendingUpdateApp] = useState(null);
610
- const [displayName, setDisplayName] = useState("");
611
- const [prompt, setPrompt] = useState("");
612
- const [providerConfiguration, setProviderConfiguration] = useState(null);
613
- const [providerConfigurationStatus, setProviderConfigurationStatus] = useState("idle");
614
- const [openFactorySettingsMenu, setOpenFactorySettingsMenu] = useState(null);
615
- const [selectedModel, setSelectedModel] = useState("");
616
- const [selectedPermissionModeId, setSelectedPermissionModeId] = useState("");
617
- const [selectedReasoningEffort, setSelectedReasoningEffort] = useState("");
825
+ const [factoryDialogOpen, setFactoryDialogOpen] = useState2(false);
826
+ const [deleteAppBusy, setDeleteAppBusy] = useState2(false);
827
+ const [pendingDeleteApp, setPendingDeleteApp] = useState2(null);
828
+ const [uninstallAppBusy, setUninstallAppBusy] = useState2(false);
829
+ const [pendingUninstallApp, setPendingUninstallApp] = useState2(null);
830
+ const [pendingLocalRepairRequest, setPendingLocalRepairRequest] = useState2(null);
831
+ const [updateAppBusy, setUpdateAppBusy] = useState2(false);
832
+ const [localRepairBusy, setLocalRepairBusy] = useState2(false);
833
+ const [pendingUpdateApp, setPendingUpdateApp] = useState2(null);
834
+ const [displayName, setDisplayName] = useState2("");
835
+ const [prompt, setPrompt] = useState2("");
836
+ const [providerConfiguration, setProviderConfiguration] = useState2(null);
837
+ const [providerConfigurationStatus, setProviderConfigurationStatus] = useState2("idle");
838
+ const [openFactorySettingsMenu, setOpenFactorySettingsMenu] = useState2(null);
839
+ const [selectedModel, setSelectedModel] = useState2("");
840
+ const [selectedPermissionModeId, setSelectedPermissionModeId] = useState2("");
841
+ const [selectedReasoningEffort, setSelectedReasoningEffort] = useState2("");
618
842
  const normalizedProviderOptions = useMemo2(
619
843
  () => providerOptions.map((option) => {
620
844
  const provider = option.provider.trim();
@@ -632,14 +856,14 @@ function AppCenterPanel({
632
856
  }).filter((option) => option != null),
633
857
  [providerOptions]
634
858
  );
635
- const [selectedProvider, setSelectedProvider] = useState(
859
+ const [selectedProvider, setSelectedProvider] = useState2(
636
860
  () => resolveDefaultAppFactoryProvider(
637
861
  normalizedProviderOptions,
638
862
  defaultAgentProvider
639
863
  )
640
864
  );
641
- const [uncontrolledActiveAppTab, setUncontrolledActiveAppTab] = useState("recommended");
642
- const [activeRecommendedCategoryTab, setActiveRecommendedCategoryTab] = useState("all");
865
+ const [uncontrolledActiveAppTab, setUncontrolledActiveAppTab] = useState2("recommended");
866
+ const [activeRecommendedCategoryTab, setActiveRecommendedCategoryTab] = useState2("all");
643
867
  const activeAppTab = controlledActiveAppTab ?? uncontrolledActiveAppTab;
644
868
  useEffect(() => {
645
869
  setSelectedProvider(
@@ -1086,6 +1310,8 @@ function AppCenterPanel({
1086
1310
  apps: activeApps,
1087
1311
  copy,
1088
1312
  emptyMessage: activeAppEmptyMessage,
1313
+ officialDeveloperIconUrl,
1314
+ showDeveloperSources,
1089
1315
  title: activeAppTabTitle
1090
1316
  }
1091
1317
  )
@@ -1630,6 +1856,8 @@ function AppCardGrid({
1630
1856
  apps,
1631
1857
  copy,
1632
1858
  emptyMessage,
1859
+ officialDeveloperIconUrl,
1860
+ showDeveloperSources,
1633
1861
  title
1634
1862
  }) {
1635
1863
  if (apps.length === 0) {
@@ -1650,7 +1878,17 @@ function AppCardGrid({
1650
1878
  "aria-label": title,
1651
1879
  className: "grid min-h-0 min-w-0 grid-cols-[repeat(auto-fill,minmax(min(100%,260px),1fr))] gap-3",
1652
1880
  role: "list",
1653
- children: apps.map((app) => /* @__PURE__ */ jsx2(AppCard, { actions, app, copy }, app.id))
1881
+ children: apps.map((app) => /* @__PURE__ */ jsx2(
1882
+ AppCard,
1883
+ {
1884
+ actions,
1885
+ app,
1886
+ copy,
1887
+ officialDeveloperIconUrl,
1888
+ showDeveloperSources
1889
+ },
1890
+ app.id
1891
+ ))
1654
1892
  }
1655
1893
  ),
1656
1894
  /* @__PURE__ */ jsx2("div", { "aria-hidden": "true", className: "h-6 shrink-0" })