@kortix/ui 0.14.9 → 0.14.11
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/_vendor/kortix-shared.js +55 -2
- package/dist/_vendor/shared/artifact-comments.d.ts +235 -0
- package/dist/_vendor/shared/channel-descriptors.d.ts +96 -0
- package/dist/_vendor/shared/channel-message.d.ts +13 -2
- package/dist/_vendor/shared/index.d.ts +1 -0
- package/dist/_vendor/shared/runtime-versions.d.ts +9 -0
- package/dist/apps/app-preview-pool.js +19 -2
- package/dist/artifacts/artifact-frame.js +7 -3
- package/dist/customize/sections/component/channel-access-panel.js +4 -4
- package/dist/customize/sections/component/telegram-channel-row.js +7 -5
- package/dist/customize/sections/view/agent-editor-catalog.js +5 -0
- package/dist/customize/sections/view/channels-view.d.ts +2 -1
- package/dist/customize/sections/view/channels-view.js +12 -7
- package/dist/customize/settings/rail.js +9 -0
- package/dist/customize/settings/settings-tabs.d.ts +1 -1
- package/dist/customize/settings/settings-tabs.js +1 -0
- package/dist/customize/settings/tabs/chrome-connections-tab.d.ts +6 -0
- package/dist/customize/settings/tabs/chrome-connections-tab.js +99 -0
- package/dist/customize/settings/tabs/sandbox-template-form.js +8 -1
- package/dist/customize/shared/catalog/catalog-empty-state.d.ts +3 -3
- package/dist/customize/shared/catalog/catalog-empty-state.js +6 -4
- package/dist/files/renderers/image-frame.d.ts +3 -1
- package/dist/files/renderers/image-frame.js +2 -2
- package/dist/files/renderers/show-content-renderer.d.ts +2 -2
- package/dist/files/renderers/show-content-renderer.js +2 -1
- package/dist/i18n/en.d.ts +206 -0
- package/dist/i18n/en.js +219 -13
- package/dist/i18n/ui-catalog/remaining-ui-translation-keys.generated.js +3 -3
- package/dist/internal/apps-and-extensions/apps-modal.d.ts +9 -0
- package/dist/internal/apps-and-extensions/apps-modal.js +107 -0
- package/dist/internal/branding/branding-provider.js +3 -3
- package/dist/internal/chrome/browser-icon.d.ts +24 -0
- package/dist/internal/chrome/browser-icon.js +58 -0
- package/dist/internal/chrome/chrome-card-state.d.ts +16 -0
- package/dist/internal/chrome/chrome-card-state.js +9 -0
- package/dist/internal/chrome/chrome-connect.d.ts +69 -0
- package/dist/internal/chrome/chrome-connect.js +178 -0
- package/dist/internal/chrome/chrome-mock.d.ts +4 -0
- package/dist/internal/chrome/chrome-mock.js +15 -0
- package/dist/internal/chrome/chrome-names.d.ts +15 -0
- package/dist/internal/chrome/chrome-names.js +35 -0
- package/dist/internal/chrome/extension.d.ts +47 -0
- package/dist/internal/chrome/extension.js +91 -0
- package/dist/internal/chrome/use-chrome-offered.d.ts +11 -0
- package/dist/internal/chrome/use-chrome-offered.js +22 -0
- package/dist/internal/connector-logo/connector-logo-source.d.ts +2 -1
- package/dist/internal/connector-logo/connector-logo-source.js +7 -6
- package/dist/internal/hooks/use-notification-center.d.ts +10 -6
- package/dist/internal/hooks/use-notification-center.js +32 -10
- package/dist/internal/icons/edge.d.ts +5 -0
- package/dist/internal/icons/edge.js +10 -0
- package/dist/internal/lib/browser-noise/rules/extensions.js +17 -3
- package/dist/internal/lib/detect-os.d.ts +43 -0
- package/dist/internal/lib/detect-os.js +70 -0
- package/dist/internal/lib/site-metadata.js +4 -4
- package/dist/internal/lib/use-project-feature-flags.js +5 -1
- package/dist/internal/notifications/notification-bell.js +4 -3
- package/dist/internal/notifications/notification-rows.d.ts +1 -1
- package/dist/internal/notifications/notification-rows.js +2 -0
- package/dist/internal/stores/apps-modal-store.d.ts +8 -0
- package/dist/internal/stores/apps-modal-store.js +6 -0
- package/dist/internal/stores/composer-prefill-store.d.ts +12 -5
- package/dist/internal/stores/kortix-computer-store.d.ts +1 -1
- package/dist/markdown/setup-links/approval-decision.js +3 -1
- package/dist/primitives/empty-state.d.ts +2 -2
- package/dist/primitives/empty-state.js +6 -5
- package/dist/primitives/mermaid-renderer.js +1 -1
- package/dist/session/composer/send-stop-control.js +8 -3
- package/dist/session/header/session-site-header.js +3 -1
- package/dist/session/panel/easy/detail-view.js +4 -0
- package/dist/session/panel/mobile-tool-drawer.js +5 -3
- package/dist/session/panel/open-session-quick-view.js +2 -2
- package/dist/session/panel/panel-fallback.js +10 -7
- package/dist/session/panel/session-audit-shared.js +3 -1
- package/dist/session/panel/session-desktop.d.ts +6 -0
- package/dist/session/panel/session-desktop.js +74 -0
- package/dist/session/panel/session-panel-provider.d.ts +1 -0
- package/dist/session/panel/session-panel-provider.js +17 -1
- package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
- package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
- package/dist/session/panel/use-session-panel-layout.js +8 -0
- package/dist/session/participants/session-participants.d.ts +3 -1
- package/dist/session/participants/session-participants.js +2 -2
- package/dist/session/prompts/approval-request.js +2 -0
- package/dist/session/status/session-tab-title.d.ts +1 -1
- package/dist/session/status/session-tab-title.js +1 -1
- package/dist/session/thread/session-chat.js +12 -0
- package/dist/session/thread/session-context-sub-sessions.js +1 -1
- package/dist/session/thread/session-thread.d.ts +13 -1
- package/dist/session/thread/session-thread.js +15 -5
- package/dist/session/thread/session-transcript-class.d.ts +5 -1
- package/dist/session/thread/session-transcript-class.js +5 -1
- package/dist/session/tool/tools/image-gen-tool.d.ts +1 -1
- package/dist/session/tool/tools/image-gen-tool.js +21 -11
- package/dist/session/turn/user-message.d.ts +7 -5
- package/dist/session/turn/user-message.js +14 -8
- package/dist/styles.css +1 -1
- package/dist/workspace/session-workspace.js +24 -5
- package/dist/workspace/sessions/session-label-badges.js +2 -1
- package/dist/workspace/sidebar/footer/project-chrome-nav.d.ts +10 -0
- package/dist/workspace/sidebar/footer/project-chrome-nav.js +56 -0
- package/dist/workspace/sidebar/project-sidebar.js +5 -1
- package/package.json +43 -3
- package/skills/kortix-ui/references/modules.md +1 -1
|
@@ -108,6 +108,7 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
108
108
|
import { useTunnelConnections } from '@kortix/sdk/react';
|
|
109
109
|
import { ComputerHero } from '../../connectors/detail/computer-app-page.js';
|
|
110
110
|
import { FeatureGateScreen } from '../../shared/feature-gate-screen.js';
|
|
111
|
+
import { CHANNEL_PLATFORM_IDS, CHANNELS, isChannelPlatform, } from '../../../_vendor/kortix-shared.js';
|
|
111
112
|
export function ChannelsSection({ projectId, platform }) {
|
|
112
113
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
113
114
|
useTeamsInstallReturnToast();
|
|
@@ -137,7 +138,7 @@ export function ChannelsSection({ projectId, platform }) {
|
|
|
137
138
|
// platform, the Channels tab every platform, one under the other.
|
|
138
139
|
const platforms = platform
|
|
139
140
|
? [platform]
|
|
140
|
-
:
|
|
141
|
+
: CHANNEL_PLATFORM_IDS.filter((pf) => pf !== 'email' || emailEnabled);
|
|
141
142
|
// The Kortix tab also holds the computer, where this deployment can pair one
|
|
142
143
|
// (the machine list answers 503 where computers are off).
|
|
143
144
|
const computersEnabled = useTunnelConnections({ refetchInterval: false }).isSuccess;
|
|
@@ -153,6 +154,11 @@ export function ChannelsSection({ projectId, platform }) {
|
|
|
153
154
|
return _jsx(EmailHero, { projectId: projectId, canWrite: canWrite });
|
|
154
155
|
case 'telegram':
|
|
155
156
|
return _jsx(TelegramChannelRow, { projectId: projectId, canWrite: canWrite });
|
|
157
|
+
default: {
|
|
158
|
+
// A new channel id fails here until it has a hero.
|
|
159
|
+
const _exhaustive = pf;
|
|
160
|
+
return _exhaustive;
|
|
161
|
+
}
|
|
156
162
|
}
|
|
157
163
|
};
|
|
158
164
|
// The Email page with the flag off: the standard flag-off screen, with its enable action.
|
|
@@ -190,14 +196,15 @@ function ChannelsButton({ count, onOpen }) {
|
|
|
190
196
|
}
|
|
191
197
|
/**
|
|
192
198
|
* What follows a connected Slack before its first channel: the one-time "now
|
|
193
|
-
* do this in Slack" nudge. It retires itself as soon as
|
|
194
|
-
*
|
|
199
|
+
* do this in Slack" nudge. It retires itself as soon as a Slack channel is
|
|
200
|
+
* bound: that is proof the user already invited the bot and mentioned it,
|
|
195
201
|
* and from then on the Slack row's "N channels" opens the bindings dialog.
|
|
202
|
+
* Another platform's row (a Telegram chat's `/model`) proves nothing about Slack.
|
|
196
203
|
*/
|
|
197
204
|
function SlackFollowUp({ projectId }) {
|
|
198
205
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
199
206
|
const bindingsQuery = useChannelBindings(projectId);
|
|
200
|
-
const bindings = bindingsQuery.data?.bindings ?? [];
|
|
207
|
+
const bindings = (bindingsQuery.data?.bindings ?? []).filter((b) => b.platform === 'slack');
|
|
201
208
|
return bindings.length === 0 && !bindingsQuery.isLoading ? (_jsxs(InfoBanner, { tone: "neutral", icon: AtIcon, title: tI18nComplete.raw('textd63af8edd3d6'), children: [tI18nComplete.raw('text1fd9ae1607aa'), ' ', _jsx("span", { className: "text-foreground font-medium", children: tI18nComplete.raw('text476b90bdc143') }), ' ', tI18nComplete.raw('textb81b729b4538')] })) : null;
|
|
202
209
|
}
|
|
203
210
|
/**
|
|
@@ -249,7 +256,7 @@ function ChannelBindingsDialog({ projectId, canWrite, platform, open, onOpenChan
|
|
|
249
256
|
return (_jsx(Modal, { open: open, onOpenChange: onOpenChange, children: selected ? (_jsx(ChannelSettingsModalContent, { projectId: projectId, binding: selected.binding, name: selected.name, scope: selected.scope, projectDefaultAgent: projectDefaultAgent, canWrite: canWrite, onBack: back })) : (_jsxs(ModalContent, { className: "lg:max-w-lg", children: [_jsxs(ModalHeader, { children: [_jsx(ModalTitle, { children: tI18nComplete.raw('text5f61b63c2c4a') }), _jsx(ModalDescription, { children: tI18nComplete.raw('text1f2550ed44dc') })] }), _jsxs(ModalBody, { className: "space-y-3 pt-4", children: [tabs.length > 1 ? (_jsx(Tabs, { value: active, onValueChange: (value) => {
|
|
250
257
|
setTab(value);
|
|
251
258
|
setQuery('');
|
|
252
|
-
}, children: _jsx(TabsListCompact, { children: tabs.map((t) => (_jsxs(TabsTriggerCompact, { value: t.platform, className: "gap-1.5", children: [
|
|
259
|
+
}, children: _jsx(TabsListCompact, { children: tabs.map((t) => (_jsxs(TabsTriggerCompact, { value: t.platform, className: "gap-1.5", children: [isChannelPlatform(t.platform) ? CHANNELS[t.platform].label : t.platform, _jsx("span", { className: "text-muted-foreground tabular-nums", children: t.count })] }, t.platform))) }) })) : null, inTab.length >= BINDING_SEARCH_MIN ? (_jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(MagnifyingGlassIcon, {}) }), _jsx(InputGroupSearchInput, { placeholder: tI18nComplete.raw('text49c266baaaa7'), value: query, onChange: (e) => setQuery(e.target.value), size: "sm" }), _jsx(InputGroupSearchClear, { onClick: () => setQuery('') })] })) : null, bindingsQuery.isLoading ? (_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-12 rounded-md" }), _jsx(Skeleton, { className: "h-12 rounded-md" })] })) : visible.length === 0 ? (_jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-xs", children: tI18nComplete.raw('texte8dd87902b91') })) : (
|
|
253
260
|
// The list scrolls; the tabs and search above it stay put. Capped at
|
|
254
261
|
// the viewport less ~21rem of header, tabs, search and footer, so
|
|
255
262
|
// the footer stays on screen at the 720 × 480 desktop minimum,
|
|
@@ -262,8 +269,6 @@ function ChannelBindingsDialog({ projectId, canWrite, platform, open, onOpenChan
|
|
|
262
269
|
_jsx(BindingOverrides, { binding: binding, policies: conversationPolicies }, "overrides"),
|
|
263
270
|
], onClick: () => setSelectedId(binding.bindingId) }, binding.bindingId))) }))] }), _jsx(ModalFooter, { className: "pt-2 pb-5", children: _jsx(Button, { type: "button", variant: "outline-ghost", onClick: () => onOpenChange(false), children: tI18nComplete.raw('text11a6767d5674') }) })] })) }));
|
|
264
271
|
}
|
|
265
|
-
/** Brand names, as the platform rows spell them. */
|
|
266
|
-
const PLATFORM_NAMES = { slack: 'Slack', teams: 'Microsoft Teams' };
|
|
267
272
|
/** A row's settings at a glance: only what overrides the project, else "Project default". */
|
|
268
273
|
function BindingOverrides({ binding, policies, }) {
|
|
269
274
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ArrowCircleUpIcon as ArrowUpCircle, ChatCircleIcon as ChatCircle, CoinsIcon as Coins, ShippingContainerIcon as Container, CreditCardIcon as CreditCard, FlaskIcon as Flask, KeyIcon as Key, LaptopIcon as Laptop, LinkIcon as Link, PaletteIcon as Palette, ShieldCheckIcon as ShieldCheck, SlidersHorizontalIcon as SlidersHorizontal, SquaresFourIcon as SquaresFour, UserIcon as User, } from '@phosphor-icons/react';
|
|
2
2
|
import { Mcp } from '../../internal/icons/mcp.js';
|
|
3
|
+
import { ChromeRailIcon } from '../../internal/chrome/browser-icon.js';
|
|
3
4
|
/**
|
|
4
5
|
* Whether a rail item is the active one for the current settings tab.
|
|
5
6
|
*
|
|
@@ -64,6 +65,14 @@ const STATIC_GROUPS = [
|
|
|
64
65
|
description: 'Language and keyboard shortcuts.',
|
|
65
66
|
icon: SlidersHorizontal,
|
|
66
67
|
},
|
|
68
|
+
{
|
|
69
|
+
tab: 'chrome',
|
|
70
|
+
label: 'Kortix in Chrome',
|
|
71
|
+
description: 'Your sessions use Chrome with your logins. You see every step.',
|
|
72
|
+
docsHref: '/docs/connect/chrome',
|
|
73
|
+
// Monochrome like its neighbours; `ChromeRailIcon` takes only `className`.
|
|
74
|
+
icon: ChromeRailIcon,
|
|
75
|
+
},
|
|
67
76
|
// Labelled "API keys", routed at `/settings/tokens`. The id has to be
|
|
68
77
|
// `tokens` — it is the URL segment, and the account page already spends
|
|
69
78
|
// `api-keys` on a legacy redirect (`RENAMED` in `settings-tabs.ts`) —
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SettingsTab = 'profile' | 'security' | 'appearance' | 'sessions' | 'preferences' | 'connected' | 'tokens' | 'plan' | 'credits' | 'workspace' | 'sandbox' | 'feature-flags' | 'upgrades' | 'mcp' | 'computer';
|
|
1
|
+
export type SettingsTab = 'profile' | 'security' | 'appearance' | 'sessions' | 'preferences' | 'connected' | 'chrome' | 'tokens' | 'plan' | 'credits' | 'workspace' | 'sandbox' | 'feature-flags' | 'upgrades' | 'mcp' | 'computer';
|
|
2
2
|
/**
|
|
3
3
|
* The default must be a tab that survives every gate, or the overlay opens on
|
|
4
4
|
* nothing. It was `general` until project configuration moved to
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings → Kortix in Chrome: the browsers this person connected with the
|
|
3
|
+
* Kortix extension. With none, the three connect steps stand inline and the
|
|
4
|
+
* connect action sits in the page header.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ChromeConnectionsTab(): import("react").JSX.Element;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useChromeConnections, useDeleteChromeConnection, useUpdateChromeConnection } from '@kortix/sdk/react';
|
|
4
|
+
import { PencilSimpleIcon } from '@phosphor-icons/react';
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { Button } from '../../../primitives/button.js';
|
|
7
|
+
import { ConfirmDialog } from '../../../primitives/confirm-dialog.js';
|
|
8
|
+
import { InlineMeta } from '../../../primitives/inline-meta.js';
|
|
9
|
+
import { Input } from '../../../primitives/input.js';
|
|
10
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../primitives/select.js';
|
|
11
|
+
import { SettingsRow, SettingsRowGroup } from '../../../primitives/settings-row.js';
|
|
12
|
+
import { SettingsSubsectionHeader } from '../../../primitives/settings-subsection-header.js';
|
|
13
|
+
import { Skeleton } from '../../../primitives/skeleton.js';
|
|
14
|
+
import { errorToast, successToast } from '../../../primitives/toast.js';
|
|
15
|
+
import { ErrorState } from '../../../primitives/error-state.js';
|
|
16
|
+
import { SettingsTabHeader } from '../settings-tab-header.js';
|
|
17
|
+
import { useLocale, useTranslations } from '../../../i18n/index.js';
|
|
18
|
+
import { relativeTime } from '../../../internal/lib/relative-time.js';
|
|
19
|
+
import { ChromeButtonMark, ChromeConnectModal, ChromeConnectSteps, ChromeCopyLink, ChromeInstallActions, ChromeOnlineDot, useChromeConnect, } from '../../../internal/chrome/chrome-connect.js';
|
|
20
|
+
import { BrowserLogoTile } from '../../../internal/chrome/browser-icon.js';
|
|
21
|
+
import { chromeDisplayNames, offlineLongerThan } from '../../../internal/chrome/chrome-names.js';
|
|
22
|
+
import { ChromeMock } from '../../../internal/chrome/chrome-mock.js';
|
|
23
|
+
const ACCESS_MODES = ['always', 'ask', 'off'];
|
|
24
|
+
// `all` first: it is the recommended default.
|
|
25
|
+
const TAB_SCOPES = ['all', 'own'];
|
|
26
|
+
/** Offline longer than this shows when the browser was last seen. Shorter is just "Offline". */
|
|
27
|
+
const LAST_SEEN_AFTER_MS = 10 * 60_000;
|
|
28
|
+
/**
|
|
29
|
+
* Settings → Kortix in Chrome: the browsers this person connected with the
|
|
30
|
+
* Kortix extension. With none, the three connect steps stand inline and the
|
|
31
|
+
* connect action sits in the page header.
|
|
32
|
+
*/
|
|
33
|
+
export function ChromeConnectionsTab() {
|
|
34
|
+
const t = useTranslations('chrome');
|
|
35
|
+
const connections = useChromeConnections();
|
|
36
|
+
const [connectOpen, setConnectOpen] = useState(false);
|
|
37
|
+
const list = connections.data ?? [];
|
|
38
|
+
if (connections.isSuccess && list.length === 0)
|
|
39
|
+
return _jsx(ChromeEmptyTab, {});
|
|
40
|
+
const names = chromeDisplayNames(list);
|
|
41
|
+
return (
|
|
42
|
+
// `max-w-2xl`: the form tier every person-scoped pane in this dialog uses.
|
|
43
|
+
_jsxs("div", { className: "mx-auto w-full max-w-2xl space-y-5", children: [_jsx(SettingsTabHeader, { tab: "chrome", action: list.length > 0 ? (_jsxs(Button, { size: "sm", variant: "secondary", className: "gap-1.5", onClick: () => setConnectOpen(true), children: [_jsx(ChromeButtonMark, {}), t('connectAnother')] })) : undefined }), connections.isPending ? (_jsx("div", { className: "space-y-2", children: _jsx(Skeleton, { className: "h-24 rounded-md" }) })) : connections.isError ? (_jsx(ErrorState, { size: "sm", title: t('loadFailed'), action: _jsx(Button, { variant: "outline", size: "sm", onClick: () => connections.refetch(), children: t('retry') }) })) : (_jsxs(_Fragment, { children: [_jsxs("section", { className: "space-y-3", children: [_jsx(SettingsSubsectionHeader, { title: t('listTitle') }), _jsx("ul", { className: "space-y-3", children: list.map((chrome) => (_jsx(ChromeRow, { chrome: chrome, displayName: names.get(chrome.id) ?? chrome.name }, chrome.id))) })] }), _jsxs("section", { className: "space-y-2", children: [_jsx(SettingsSubsectionHeader, { title: t('how.title') }), _jsxs("ul", { className: "text-muted-foreground list-disc space-y-1 pl-5 text-sm text-pretty", children: [_jsx("li", { children: t('how.group') }), _jsx("li", { children: t('how.control') }), _jsx("li", { children: t('how.own') })] })] })] })), _jsx(ChromeConnectModal, { open: connectOpen, onOpenChange: setConnectOpen, fresh: true })] }));
|
|
44
|
+
}
|
|
45
|
+
/** No browser yet: the steps stand inline, the one action is in the header. */
|
|
46
|
+
function ChromeEmptyTab() {
|
|
47
|
+
const state = useChromeConnect();
|
|
48
|
+
return (_jsxs("div", { className: "mx-auto w-full max-w-2xl space-y-5", children: [_jsx(SettingsTabHeader, { tab: "chrome", action: state.environment === 'unsupported' ? (_jsx(ChromeCopyLink, {})) : (_jsx(ChromeInstallActions, { state: state, showGuide: false })) }), _jsxs("div", { className: "space-y-4", children: [_jsx(ChromeMock, { className: "mx-auto max-w-md" }), _jsx("div", { className: "bg-popover flex flex-col gap-5 rounded-md border px-4 py-5", children: _jsx(ChromeConnectSteps, { state: state, showActions: false }) })] })] }));
|
|
49
|
+
}
|
|
50
|
+
/** "Chromium on macOS · Work · v1.0.0": what tells two browsers apart. The name is not repeated. */
|
|
51
|
+
function chromeMeta(chrome, displayName) {
|
|
52
|
+
// The profile label already sits in the display name of a duplicate: say it once.
|
|
53
|
+
const profile = chrome.profileLabel && !displayName.includes(chrome.profileLabel) ? chrome.profileLabel : null;
|
|
54
|
+
return [profile, chrome.extensionVersion ? `v${chrome.extensionVersion}` : null].filter((part) => Boolean(part));
|
|
55
|
+
}
|
|
56
|
+
function ChromeRow({ chrome, displayName }) {
|
|
57
|
+
const t = useTranslations('chrome');
|
|
58
|
+
const locale = useLocale();
|
|
59
|
+
const update = useUpdateChromeConnection();
|
|
60
|
+
const remove = useDeleteChromeConnection();
|
|
61
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
62
|
+
const [draft, setDraft] = useState(null);
|
|
63
|
+
const save = (change) => update.mutate({ id: chrome.id, ...change }, { onError: () => errorToast(t('updateFailed')) });
|
|
64
|
+
const commitName = () => {
|
|
65
|
+
const name = draft?.trim();
|
|
66
|
+
// An empty name is not a name: keep the field open and flag it. Blur reverts.
|
|
67
|
+
if (!name)
|
|
68
|
+
return false;
|
|
69
|
+
setDraft(null);
|
|
70
|
+
if (name !== chrome.name)
|
|
71
|
+
save({ name });
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
const sessions = chrome.activeSessions.length;
|
|
75
|
+
// "Last seen just now" beside "Offline" contradicts itself: say it only once it is a while ago.
|
|
76
|
+
const longOffline = !chrome.online && offlineLongerThan(chrome.lastSeenAt, LAST_SEEN_AFTER_MS);
|
|
77
|
+
const status = chrome.online
|
|
78
|
+
? t('online')
|
|
79
|
+
: longOffline
|
|
80
|
+
? t('lastSeen', { time: relativeTime(chrome.lastSeenAt, locale) })
|
|
81
|
+
: t('offline');
|
|
82
|
+
const meta = [...chromeMeta(chrome, displayName), status];
|
|
83
|
+
return (_jsxs("li", { children: [_jsxs(SettingsRowGroup, { children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [_jsx(BrowserLogoTile, { brand: chrome.brand }), _jsxs("div", { className: "min-w-0 flex-1 space-y-0.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ChromeOnlineDot, { online: chrome.online }), draft === null ? (_jsxs("button", { type: "button", "aria-label": t('rename', { name: displayName }), title: displayName, className: "focus-visible:ring-ring/50 -mx-1 flex min-w-0 items-center gap-1.5 rounded-sm px-1 text-left outline-none focus-visible:ring-3", onClick: () => setDraft(chrome.name), children: [_jsx("span", { className: "truncate text-sm font-medium", children: displayName }), _jsx(PencilSimpleIcon, { className: "text-muted-foreground size-3.5 shrink-0" })] })) : (_jsx(Input, { autoFocus: true, value: draft, maxLength: 255, "aria-label": t('renameLabel'), className: "h-6 py-0", "aria-invalid": draft.trim() === '' ? true : undefined, onChange: (event) => setDraft(event.target.value), onBlur: () => {
|
|
84
|
+
if (!commitName())
|
|
85
|
+
setDraft(null);
|
|
86
|
+
}, onKeyDown: (event) => {
|
|
87
|
+
if (event.key === 'Enter')
|
|
88
|
+
commitName();
|
|
89
|
+
// Closes the field only. The settings dialog ignores Escape from a text field.
|
|
90
|
+
if (event.key === 'Escape')
|
|
91
|
+
setDraft(null);
|
|
92
|
+
} }))] }), _jsxs(InlineMeta, { children: [meta, sessions > 0 ? t('usedBy', { count: sessions }) : ''] })] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setConfirmOpen(true), children: t('disconnect') })] }), _jsx(SettingsRow, { label: t('access.label'), description: t('access.description'), children: _jsxs(Select, { value: chrome.accessMode, disabled: update.isPending, onValueChange: (value) => save({ accessMode: value }), children: [_jsx(SelectTrigger, { size: "sm", className: "w-44", "aria-label": t('access.label'), children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: ACCESS_MODES.map((mode) => (_jsx(SelectItem, { value: mode, children: t(`access.${mode}`) }, mode))) })] }) }), _jsx(SettingsRow, { label: t('scope.label'), description: t('scope.description'), children: _jsxs(Select, { value: chrome.tabScope, disabled: update.isPending, onValueChange: (value) => save({ tabScope: value }), children: [_jsx(SelectTrigger, { size: "sm", className: "w-44", "aria-label": t('scope.label'), children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: TAB_SCOPES.map((scope) => (_jsx(SelectItem, { value: scope, children: t(`scope.${scope}`) }, scope))) })] }) })] }), _jsx(ConfirmDialog, { open: confirmOpen, onOpenChange: setConfirmOpen, title: t('disconnectTitle', { name: displayName }), description: _jsxs(_Fragment, { children: [_jsx("span", { className: "text-muted-foreground mb-2 block text-xs", children: meta.join(' · ') }), t('disconnectDescription')] }), confirmLabel: t('disconnect'), confirmVariant: "destructive", isPending: remove.isPending, onConfirm: () => remove.mutate(chrome.id, {
|
|
93
|
+
onSuccess: () => {
|
|
94
|
+
setConfirmOpen(false);
|
|
95
|
+
successToast(t('disconnected'));
|
|
96
|
+
},
|
|
97
|
+
onError: () => errorToast(t('disconnectFailed')),
|
|
98
|
+
}) })] }));
|
|
99
|
+
}
|
|
@@ -13,6 +13,7 @@ import { ShippingContainerIcon as Container, FileCodeIcon as FileCode, PackageIc
|
|
|
13
13
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
14
14
|
import { useMemo, useState } from 'react';
|
|
15
15
|
import { Button } from '../../../primitives/button.js';
|
|
16
|
+
import { Checkbox } from '../../../primitives/checkbox.js';
|
|
16
17
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../../../primitives/dialog.js';
|
|
17
18
|
import { Input } from '../../../primitives/input.js';
|
|
18
19
|
import { Label } from '../../../primitives/label.js';
|
|
@@ -48,6 +49,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
|
|
|
48
49
|
const [cpu, setCpu] = useState('2');
|
|
49
50
|
const [memoryGb, setMemoryGb] = useState('4');
|
|
50
51
|
const [diskGb, setDiskGb] = useState('20');
|
|
52
|
+
const [residentialProxy, setResidentialProxy] = useState(false);
|
|
51
53
|
// Reset / hydrate when opening
|
|
52
54
|
const [hydratedFor, setHydratedFor] = useState(null);
|
|
53
55
|
if (!hydratedFor || hydratedFor[0] !== open || hydratedFor[1] !== template) {
|
|
@@ -70,6 +72,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
|
|
|
70
72
|
setCpu(String(template.cpu));
|
|
71
73
|
setMemoryGb(String(template.memory_gb));
|
|
72
74
|
setDiskGb(String(template.disk_gb));
|
|
75
|
+
setResidentialProxy(!!template.residential_proxy);
|
|
73
76
|
}
|
|
74
77
|
else if (open) {
|
|
75
78
|
setName('');
|
|
@@ -82,6 +85,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
|
|
|
82
85
|
setCpu('2');
|
|
83
86
|
setMemoryGb('6');
|
|
84
87
|
setDiskGb('20');
|
|
88
|
+
setResidentialProxy(false);
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
// Auto-slug from name when the user hasn't typed a slug manually yet.
|
|
@@ -117,6 +121,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
|
|
|
117
121
|
cpu: parsePosInt(cpu),
|
|
118
122
|
memory_gb: parsePosInt(memoryGb),
|
|
119
123
|
disk_gb: parsePosInt(diskGb),
|
|
124
|
+
...(residentialProxy ? { residential_proxy: true } : {}),
|
|
120
125
|
}),
|
|
121
126
|
onSuccess: () => {
|
|
122
127
|
successToast(t('toasts.created'));
|
|
@@ -135,6 +140,8 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
|
|
|
135
140
|
cpu: parsePosInt(cpu) ?? null,
|
|
136
141
|
memory_gb: parsePosInt(memoryGb) ?? null,
|
|
137
142
|
disk_gb: parsePosInt(diskGb) ?? null,
|
|
143
|
+
// Keep a country set through kortix.yaml or the API while it stays on.
|
|
144
|
+
residential_proxy: residentialProxy ? (template?.residential_proxy ?? true) : null,
|
|
138
145
|
}),
|
|
139
146
|
onSuccess: () => {
|
|
140
147
|
successToast(t('toasts.updated'));
|
|
@@ -148,7 +155,7 @@ export function SandboxTemplateForm({ projectId, open, onOpenChange, template, }
|
|
|
148
155
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(Container, { className: "size-4" }), isEdit ? t('editTitle', { name: template?.name ?? '' }) : t('newTitle')] }), _jsx(DialogDescription, { children: t('description') })] }), _jsxs("div", { className: "space-y-4 py-2", children: [_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { children: [_jsx(Label, { htmlFor: "tpl-name", children: t('name') }), _jsx(Input, { id: "tpl-name", placeholder: t('namePlaceholder'), value: name, onChange: (e) => setName(e.target.value) })] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "tpl-slug", children: t('slug') }), _jsx(Input, { id: "tpl-slug", placeholder: tI18nComplete.raw('text5d58d41913d9'), value: slug, onChange: (e) => {
|
|
149
156
|
setSlugManuallyEdited(true);
|
|
150
157
|
setSlug(e.target.value.toLowerCase());
|
|
151
|
-
}, disabled: isEdit, "aria-invalid": !!slugError }), slugError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: slugError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('imageSource') }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2", children: [_jsxs("button", { type: "button", onClick: () => setMode('image'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'image' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "size-4" }), _jsx("span", { className: "font-medium", children: t('publicImage') })] }), _jsxs("span", { className: "text-muted-foreground text-xs", children: [t('example'), " ", _jsx("code", { className: "font-mono", children: "python:3.12-slim" })] })] }), _jsxs("button", { type: "button", onClick: () => setMode('dockerfile'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'dockerfile' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileCode, { className: "size-4" }), _jsx("span", { className: "font-medium", children: tI18nComplete.raw('textdd2c0eb6ea5c') })] }), _jsx("span", { className: "text-muted-foreground text-xs", children: t('pathInsideRepo') })] })] }), _jsxs("div", { className: "mt-3", children: [mode === 'image' ? (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-image", children: t('image') }), _jsx(Input, { id: "tpl-image", placeholder: tI18nComplete.raw('text1d0447543fac'), value: image, onChange: (e) => setImage(e.target.value) }), _jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: [t('specificTag'), " (", _jsx("code", { className: "font-mono", children: "latest" }), ")."] })] })) : (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-df", children: t('dockerfilePath') }), _jsx(Input, { id: "tpl-df", placeholder: tI18nComplete.raw('text1bc7a47dada1'), value: dockerfilePath, onChange: (e) => setDockerfilePath(e.target.value) }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: t('relativeToRoot') })] })), sourceError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: sourceError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('resources') }), _jsxs("div", { className: "mt-2 grid grid-cols-3 gap-3", children: [_jsx(NumericField, { id: "cpu", label: tI18nComplete.raw('text082591a16602'), value: cpu, onChange: setCpu, min: 1, max: 32 }), _jsx(NumericField, { id: "mem", label: t('memory'), value: memoryGb, onChange: setMemoryGb, min: 1, max: 128 }), _jsx(NumericField, { id: "disk", label: t('disk'), value: diskGb, onChange: setDiskGb, min: 1, max: 500 })] })] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: "tpl-entry", children: [t('entrypoint'), " ", _jsxs("span", { className: "text-muted-foreground", children: ["(", t('optional'), ")"] })] }), _jsx(Textarea, { id: "tpl-entry", placeholder: t('entrypointPlaceholder'), value: entrypoint, onChange: (e) => setEntrypoint(e.target.value), className: "font-mono text-xs", rows: 2 })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: submitting, children: t('cancel') }), _jsxs(Button, { onClick: () => (isEdit ? editMut.mutate() : createMut.mutate()), disabled: !canSubmit || submitting, children: [submitting && _jsx(Loading, { className: "mr-2 size-4" }), isEdit ? t('save') : t('create')] })] })] }) }));
|
|
158
|
+
}, disabled: isEdit, "aria-invalid": !!slugError }), slugError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: slugError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('imageSource') }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2", children: [_jsxs("button", { type: "button", onClick: () => setMode('image'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'image' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Package, { className: "size-4" }), _jsx("span", { className: "font-medium", children: t('publicImage') })] }), _jsxs("span", { className: "text-muted-foreground text-xs", children: [t('example'), " ", _jsx("code", { className: "font-mono", children: "python:3.12-slim" })] })] }), _jsxs("button", { type: "button", onClick: () => setMode('dockerfile'), className: cn('border-border/60 flex flex-col items-start gap-1 rounded-md border p-3 text-left text-sm transition-colors', mode === 'dockerfile' && 'border-foreground/30 bg-muted/40'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileCode, { className: "size-4" }), _jsx("span", { className: "font-medium", children: tI18nComplete.raw('textdd2c0eb6ea5c') })] }), _jsx("span", { className: "text-muted-foreground text-xs", children: t('pathInsideRepo') })] })] }), _jsxs("div", { className: "mt-3", children: [mode === 'image' ? (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-image", children: t('image') }), _jsx(Input, { id: "tpl-image", placeholder: tI18nComplete.raw('text1d0447543fac'), value: image, onChange: (e) => setImage(e.target.value) }), _jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: [t('specificTag'), " (", _jsx("code", { className: "font-mono", children: "latest" }), ")."] })] })) : (_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "tpl-df", children: t('dockerfilePath') }), _jsx(Input, { id: "tpl-df", placeholder: tI18nComplete.raw('text1bc7a47dada1'), value: dockerfilePath, onChange: (e) => setDockerfilePath(e.target.value) }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: t('relativeToRoot') })] })), sourceError && _jsx("p", { className: "text-destructive mt-1 text-xs", children: sourceError })] })] }), _jsxs("div", { children: [_jsx(Label, { children: t('resources') }), _jsxs("div", { className: "mt-2 grid grid-cols-3 gap-3", children: [_jsx(NumericField, { id: "cpu", label: tI18nComplete.raw('text082591a16602'), value: cpu, onChange: setCpu, min: 1, max: 32 }), _jsx(NumericField, { id: "mem", label: t('memory'), value: memoryGb, onChange: setMemoryGb, min: 1, max: 128 }), _jsx(NumericField, { id: "disk", label: t('disk'), value: diskGb, onChange: setDiskGb, min: 1, max: 500 })] })] }), _jsxs("div", { children: [_jsxs(Label, { htmlFor: "tpl-entry", children: [t('entrypoint'), " ", _jsxs("span", { className: "text-muted-foreground", children: ["(", t('optional'), ")"] })] }), _jsx(Textarea, { id: "tpl-entry", placeholder: t('entrypointPlaceholder'), value: entrypoint, onChange: (e) => setEntrypoint(e.target.value), className: "font-mono text-xs", rows: 2 })] }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Checkbox, { id: "tpl-residential-ip", className: "mt-0.5", checked: residentialProxy, onCheckedChange: (checked) => setResidentialProxy(checked === true) }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "tpl-residential-ip", children: t('residentialIp') }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: t('residentialIpHint') })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: submitting, children: t('cancel') }), _jsxs(Button, { onClick: () => (isEdit ? editMut.mutate() : createMut.mutate()), disabled: !canSubmit || submitting, children: [submitting && _jsx(Loading, { className: "mr-2 size-4" }), isEdit ? t('save') : t('create')] })] })] }) }));
|
|
152
159
|
}
|
|
153
160
|
function NumericField({ id, label, value, onChange, min, max, }) {
|
|
154
161
|
return (_jsxs("div", { children: [_jsx(Label, { htmlFor: id, className: "text-xs", children: label }), _jsx(Input, { id: id, type: "number", min: min, max: max, value: value, onChange: (e) => onChange(e.target.value) })] }));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
/**
|
|
3
|
-
* The "nothing here"
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* The "nothing here" state every catalog uses: the shared `EmptyState`, so a
|
|
4
|
+
* catalog's empty slot looks like every other empty view. `CatalogGrid` owns
|
|
5
|
+
* loading/error/empty; this is what callers put IN the empty slot when the
|
|
6
6
|
* cause is the search box rather than an empty project.
|
|
7
7
|
*/
|
|
8
8
|
export declare function CatalogEmptyNote({ children }: {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { MagnifyingGlassIcon } from '@phosphor-icons/react';
|
|
2
3
|
import { useTranslations } from '../../../i18n/index.js';
|
|
4
|
+
import { EmptyState } from '../../../primitives/empty-state.js';
|
|
3
5
|
/**
|
|
4
|
-
* The "nothing here"
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* The "nothing here" state every catalog uses: the shared `EmptyState`, so a
|
|
7
|
+
* catalog's empty slot looks like every other empty view. `CatalogGrid` owns
|
|
8
|
+
* loading/error/empty; this is what callers put IN the empty slot when the
|
|
7
9
|
* cause is the search box rather than an empty project.
|
|
8
10
|
*/
|
|
9
11
|
export function CatalogEmptyNote({ children }) {
|
|
10
|
-
return _jsx(
|
|
12
|
+
return _jsx(EmptyState, { size: "sm", icon: MagnifyingGlassIcon, title: children });
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* "No matches for `<query>`."
|
|
@@ -27,6 +27,8 @@ export interface ImageFrameProps {
|
|
|
27
27
|
naturalRatio?: string | null;
|
|
28
28
|
/** Reports the image's natural size once it decoded. */
|
|
29
29
|
onNaturalSize?: (width: number, height: number) => void;
|
|
30
|
+
/** Drawn over the frame's own box (an overlay on the image's corner), which narrows with `maxHeight`. */
|
|
31
|
+
children?: ReactNode;
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* The box an image takes before, while and after it exists: a flat `bg-muted`
|
|
@@ -35,7 +37,7 @@ export interface ImageFrameProps {
|
|
|
35
37
|
* The frame itself never moves: the busy signal is the label (one waiting
|
|
36
38
|
* channel, kortix-brand `layout.md` States).
|
|
37
39
|
*/
|
|
38
|
-
export declare function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill, maxHeight, openLabel, naturalRatio, onNaturalSize, }: ImageFrameProps): import("react").JSX.Element;
|
|
40
|
+
export declare function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill, maxHeight, openLabel, naturalRatio, onNaturalSize, children, }: ImageFrameProps): import("react").JSX.Element;
|
|
39
41
|
/**
|
|
40
42
|
* An image whose file could not be read: "Image unavailable" and Try again,
|
|
41
43
|
* centered in the frame. Never the read's own error text (R38): that is
|
|
@@ -16,7 +16,7 @@ function ratioValue(ratio) {
|
|
|
16
16
|
* The frame itself never moves: the busy signal is the label (one waiting
|
|
17
17
|
* channel, kortix-brand `layout.md` States).
|
|
18
18
|
*/
|
|
19
|
-
export function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill = false, maxHeight, openLabel, naturalRatio = null, onNaturalSize, }) {
|
|
19
|
+
export function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, className, fill = false, maxHeight, openLabel, naturalRatio = null, onNaturalSize, children, }) {
|
|
20
20
|
const img = useRef(null);
|
|
21
21
|
const [loaded, setLoaded] = useState(false);
|
|
22
22
|
const [natural, setNatural] = useState(naturalRatio);
|
|
@@ -46,7 +46,7 @@ export function ImageFrame({ aspectRatio, src, alt, pending, label, onOpen, clas
|
|
|
46
46
|
: {
|
|
47
47
|
aspectRatio: natural ?? aspectRatio,
|
|
48
48
|
...(maxHeight && { width: `min(100%, calc(${maxHeight} * ${ratioValue(natural ?? aspectRatio)}))` }),
|
|
49
|
-
}, children: [image && onOpen ? (_jsx("button", { type: "button", onClick: onOpen, "aria-label": openLabel ?? alt, className: "focus-visible:ring-kortix-base block size-full cursor-zoom-in rounded-md focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset", children: image })) : (image), label ? (_jsx("div", { className: "absolute inset-0 flex items-center justify-center p-4 text-center", children: label })) : null] }));
|
|
49
|
+
}, children: [image && onOpen ? (_jsx("button", { type: "button", onClick: onOpen, "aria-label": openLabel ?? alt, className: "focus-visible:ring-kortix-base block size-full cursor-zoom-in rounded-md focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset", children: image })) : (image), label ? (_jsx("div", { className: "absolute inset-0 flex items-center justify-center p-4 text-center", children: label })) : null, children] }));
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* An image whose file could not be read: "Image unavailable" and Try again,
|
|
@@ -30,7 +30,7 @@ export interface ShowContentProps {
|
|
|
30
30
|
type: string;
|
|
31
31
|
title?: string;
|
|
32
32
|
description?: string;
|
|
33
|
-
path?: string;
|
|
33
|
+
path?: string | null;
|
|
34
34
|
url?: string;
|
|
35
35
|
content?: string;
|
|
36
36
|
language?: string;
|
|
@@ -80,7 +80,7 @@ export interface ShowContentProps {
|
|
|
80
80
|
*/
|
|
81
81
|
toolbarActions?: React.ReactNode;
|
|
82
82
|
}
|
|
83
|
-
export declare function ShowContentRenderer({ type, title, description, path, url, content, language, aspectRatio, attachment, sha256, LocalhostPreview, fill, onStatusChange, toolbarActions, }: ShowContentProps): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
|
|
83
|
+
export declare function ShowContentRenderer({ type, title, description, path: inputPath, url, content, language, aspectRatio, attachment, sha256, LocalhostPreview, fill, onStatusChange, toolbarActions, }: ShowContentProps): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
|
|
84
84
|
export interface ShowCarouselItem {
|
|
85
85
|
type: string;
|
|
86
86
|
title?: string;
|
|
@@ -172,7 +172,8 @@ function FileCard({ title, fileName, path }) {
|
|
|
172
172
|
return (_jsxs("div", { className: "flex items-center gap-4 px-5 py-5", children: [_jsx("div", { className: "bg-muted/20 flex size-12 items-center justify-center rounded-xl", children: _jsx(FileText, { className: "text-muted-foreground/40 size-6" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-foreground truncate text-sm font-medium", children: title || fileName }), _jsx("div", { className: "text-muted-foreground/50 mt-0.5 truncate font-mono text-xs", children: path })] })] }));
|
|
173
173
|
}
|
|
174
174
|
// ── Component ──────────────────────────────────────────────────────────────
|
|
175
|
-
export function ShowContentRenderer({ type, title = '', description = '', path
|
|
175
|
+
export function ShowContentRenderer({ type, title = '', description = '', path: inputPath, url = '', content = '', language = '', aspectRatio = '', attachment, sha256, LocalhostPreview, fill = false, onStatusChange, toolbarActions, }) {
|
|
176
|
+
const path = inputPath ?? '';
|
|
176
177
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
177
178
|
const tImageGen = useTranslations('imageGen');
|
|
178
179
|
const arCSS = showAspectRatioToCSS(aspectRatio);
|