@kortix/ui 0.14.5 → 0.14.6
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/access/access/access-dialog.js +8 -5
- package/dist/access/access/copy-row.js +2 -2
- package/dist/access/sharing-picker.js +2 -1
- package/dist/browser/app-in-browser.js +5 -3
- package/dist/browser/browser-panel.js +6 -6
- package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
- package/dist/browser/sandbox-browser-chrome.js +4 -3
- package/dist/customize/agents/agent-access.d.ts +20 -0
- package/dist/customize/agents/agent-access.js +17 -0
- package/dist/customize/agents/agent-grant-pages.js +5 -6
- package/dist/customize/agents/agent-page.js +2 -1
- package/dist/customize/agents/agents-page.js +18 -1
- package/dist/customize/connectors/add-mcp/add-mcp-server.js +4 -2
- package/dist/customize/connectors/connector-routes.d.ts +3 -1
- package/dist/customize/connectors/connector-routes.js +18 -0
- package/dist/customize/connectors/connectors-page.js +23 -4
- package/dist/customize/connectors/detail/connector-advanced-tools.js +4 -6
- package/dist/customize/providers/provider-accordion.js +2 -1
- package/dist/customize/providers/provider-connect.js +2 -1
- package/dist/customize/sections/component/telegram-channel-row.js +1 -1
- package/dist/customize/sections/connector-connection-modal.js +11 -9
- package/dist/customize/sections/connectors-view.js +11 -13
- package/dist/customize/sections/view/gateway/gateway-routing.js +15 -18
- package/dist/customize/settings/rail-copy-context.js +5 -3
- package/dist/customize/settings/tabs/sandbox-template-form.js +9 -12
- package/dist/customize/shared/capability-tabs.js +2 -1
- package/dist/customize/skills/skills-page.js +3 -5
- package/dist/customize/triggers/schedule/event-trigger-copy.js +3 -2
- package/dist/customize/triggers/schedule/schedule-builder.js +7 -4
- package/dist/customize/triggers/schedule/trigger-composer.js +5 -2
- package/dist/customize/triggers/schedule/when-event.js +1 -1
- package/dist/diff/diff-view.js +9 -10
- package/dist/files/editor/code-editor.js +2 -3
- package/dist/files/project/components/change-request-detail-dialog.d.ts +3 -1
- package/dist/files/project/components/change-request-detail-dialog.js +3 -5
- package/dist/files/project/components/change-request-status-badge.d.ts +12 -0
- package/dist/files/project/components/change-request-status-badge.js +27 -0
- package/dist/files/project/components/change-requests-panel.js +1 -1
- package/dist/files/project/components/drive-explorer.js +2 -1
- package/dist/files/project/components/drive-grid-view.js +11 -7
- package/dist/files/project/components/drive-list-view.js +4 -4
- package/dist/files/project/components/file-breadcrumbs.js +1 -1
- package/dist/files/project/components/file-explorer-page.js +1 -1
- package/dist/files/project/components/file-history-popover.js +1 -6
- package/dist/files/project/components/file-search.js +6 -1
- package/dist/files/project/components/open-change-request-dialog.js +2 -1
- package/dist/files/project/components/version-selector-logic.d.ts +11 -0
- package/dist/files/project/components/version-selector-logic.js +26 -0
- package/dist/files/project/components/version-selector.js +11 -20
- package/dist/files/project/hooks/use-server-health.js +17 -15
- package/dist/files/renderers/docx/docx-viewer.js +0 -5
- package/dist/files/renderers/image-renderer.js +6 -24
- package/dist/files/renderers/pdf/pdf-viewer.js +1 -1
- package/dist/files/renderers/presentation/FullScreenPresentationViewer.js +2 -11
- package/dist/files/renderers/show-content-renderer.js +1 -1
- package/dist/files/renderers/sqlite-renderer.js +8 -3
- package/dist/files/renderers/xlsx/xlsx-viewer.js +15 -16
- package/dist/files/tree/components/file-tree-item.js +1 -1
- package/dist/files/viewer/file-preview-modal.js +1 -1
- package/dist/files/viewer/use-file-content-state.js +1 -1
- package/dist/i18n/en.d.ts +10 -0
- package/dist/i18n/en.js +10 -0
- package/dist/internal/desktop/desktop-back-button.js +5 -3
- package/dist/internal/hooks/use-auto-scroll.js +5 -3
- package/dist/internal/notifications/notification-rows.js +2 -1
- package/dist/internal/outcomes/outcome-types.d.ts +5 -0
- package/dist/markdown/code/inline-code.js +8 -0
- package/dist/markdown/code/output-paths-context.d.ts +18 -0
- package/dist/markdown/code/output-paths-context.js +35 -0
- package/dist/markdown/genui/components/charts.js +6 -5
- package/dist/primitives/map.js +2 -1
- package/dist/primitives/preview-image.js +4 -2
- package/dist/primitives/textarea.js +1 -1
- package/dist/primitives/typed-title.js +12 -14
- package/dist/primitives/typing-animation.js +4 -2
- package/dist/session/composer/agent-selector.js +6 -1
- package/dist/session/composer/composer-chat-input.js +5 -3
- package/dist/session/composer/composer-logic.js +8 -7
- package/dist/session/composer/draft/composer-draft.js +4 -4
- package/dist/session/composer/pasted-text.js +3 -2
- package/dist/session/composer/stores/queued-draft-store.js +3 -2
- package/dist/session/header/reminder-parts.d.ts +11 -2
- package/dist/session/header/reminder-parts.js +9 -4
- package/dist/session/media/sandbox-url-detector.js +0 -7
- package/dist/session/outcomes/change-request-outcomes.js +24 -3
- package/dist/session/overrides/provider-secret-pool-editor.js +2 -1
- package/dist/session/panel/advanced/advanced-panel.js +3 -7
- package/dist/session/panel/easy/app-preview.js +12 -2
- package/dist/session/panel/easy/easy-panel-logic.d.ts +1 -1
- package/dist/session/panel/easy/easy-panel-logic.js +1 -1
- package/dist/session/panel/easy/outputs-card.js +2 -1
- package/dist/session/panel/easy/panel-card.js +5 -3
- package/dist/session/panel/easy/preview-load.d.ts +14 -0
- package/dist/session/panel/easy/preview-load.js +15 -0
- package/dist/session/panel/easy/step-detail-body.js +2 -7
- package/dist/session/panel/session-panel-provider.js +6 -2
- package/dist/session/panel/shared/use-panel-derivations.js +6 -4
- package/dist/session/panel/use-session-panel-layout.js +7 -5
- package/dist/session/print/session-print-header.js +9 -8
- package/dist/session/thread/session-chat.js +12 -9
- package/dist/session/thread/session-history.js +3 -1
- package/dist/session/thread/transcript.js +13 -7
- package/dist/session/tool/shared/file-list.js +19 -2
- package/dist/session/tool/shared/infrastructure-contexts.d.ts +7 -0
- package/dist/session/tool/shared/infrastructure-contexts.js +7 -0
- package/dist/session/tool/shared/infrastructure-output.js +9 -5
- package/dist/session/tool/shared/infrastructure-row-header.js +9 -1
- package/dist/session/tool/shared/infrastructure-rows.js +7 -5
- package/dist/session/tool/tools/bash-tool.js +10 -6
- package/dist/session/tool/tools/web-search-tool.js +25 -3
- package/dist/session/turn/recovered-failures.d.ts +8 -0
- package/dist/session/turn/recovered-failures.js +32 -0
- package/dist/session/turn/streaming-cadence.js +5 -8
- package/dist/styles.css +1 -1
- package/dist/terminal/session-terminal-panel.js +3 -5
- package/dist/theme/theme.js +2 -1
- package/dist/workspace/projects/modal/github-import-pickers.js +7 -6
- package/dist/workspace/review/review-center.js +13 -10
- package/dist/workspace/session-workspace.js +5 -9
- package/dist/workspace/sessions/deleted-session-row.js +5 -2
- package/dist/workspace/sidebar/footer/project-computer-nav.js +6 -4
- package/dist/workspace/sidebar/footer/project-mcp-nav.js +6 -2
- package/dist/workspace/sidebar/modal/rename-session-modal.js +8 -3
- package/package.json +3 -3
|
@@ -86,9 +86,11 @@ export function diffAgentGrants(current, next) {
|
|
|
86
86
|
if (next.mode === 'all')
|
|
87
87
|
return { add: [], remove: [...currentIds] };
|
|
88
88
|
const nextIds = next.ids;
|
|
89
|
+
const currentSet = new Set(currentIds);
|
|
90
|
+
const nextSet = new Set(nextIds);
|
|
89
91
|
return {
|
|
90
|
-
add: nextIds.filter((id) => !
|
|
91
|
-
remove: currentIds.filter((id) => !
|
|
92
|
+
add: nextIds.filter((id) => !currentSet.has(id)),
|
|
93
|
+
remove: currentIds.filter((id) => !nextSet.has(id)),
|
|
92
94
|
};
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
@@ -339,10 +341,11 @@ function PrincipalAccessDialog({ open, onOpenChange, accountId, scope, mode, acc
|
|
|
339
341
|
...contract('inventory'),
|
|
340
342
|
});
|
|
341
343
|
const projectAgents = useMemo(() => resourceGrantsQuery.data?.resources.agents ?? [], [resourceGrantsQuery.data]);
|
|
344
|
+
const selectedAgentIdSet = useMemo(() => new Set(agents.ids), [agents.ids]);
|
|
342
345
|
const selectedAgentDeclares = useMemo(() => {
|
|
343
346
|
if (agents.mode !== 'subset' || agents.ids.length === 0)
|
|
344
347
|
return null;
|
|
345
|
-
const picked = projectAgents.filter((a) =>
|
|
348
|
+
const picked = projectAgents.filter((a) => selectedAgentIdSet.has(a.id));
|
|
346
349
|
if (picked.length === 0)
|
|
347
350
|
return null;
|
|
348
351
|
const union = (key) => {
|
|
@@ -351,7 +354,7 @@ function PrincipalAccessDialog({ open, onOpenChange, accountId, scope, mode, acc
|
|
|
351
354
|
return [...new Set(picked.flatMap((a) => a.declares?.[key] ?? []))];
|
|
352
355
|
};
|
|
353
356
|
return { secrets: union('secrets'), connectors: union('connectors') };
|
|
354
|
-
}, [agents, projectAgents]);
|
|
357
|
+
}, [agents, projectAgents, selectedAgentIdSet]);
|
|
355
358
|
// ── Expiry ────────────────────────────────────────────────────────────
|
|
356
359
|
// `expires_at` is a column on every assignment, so every grant this dialog
|
|
357
360
|
// writes can carry one. It used to be offered only where the underlying store
|
|
@@ -879,7 +882,7 @@ function PrincipalAccessDialog({ open, onOpenChange, accountId, scope, mode, acc
|
|
|
879
882
|
? ['member', 'admin']
|
|
880
883
|
: undefined }), scope.kind === 'project' && principals.groupIds.length > 0 ? (_jsx(FieldDescription, { children: tI18nComplete.raw('text237f1a28cf06') })) : null] })) : null, scope.kind === 'project' && everyoneSelected ? (_jsx(InfoBanner, { tone: "neutral", icon: UsersThreeIcon, children: tSharing('everyoneRoleNote', { project: scope.projectName }) })) : null, scope.kind === 'project' && showAgentCeilingNote ? (_jsx(InfoBanner, { tone: "neutral", icon: RobotIcon, title: tAgents('ceilingNoteTitle'), children: _jsx("span", { "data-testid": "agent-ceiling-note", children: tAgents('ceilingNote') }) })) : null, showAgents ? (_jsxs(Field, { className: "gap-1.5", children: [_jsx(FieldLabel, { children: tI18nComplete.raw('text279b44d2ab4b') }), resourceGrantsQuery.isError ? (_jsx(InfoBanner, { tone: "destructive", action: _jsx(Button, { size: "sm", variant: "outline", onClick: () => void resourceGrantsQuery.refetch(), children: tCommon('retry') }), children: resourceGrantsQuery.error.message })) : resourceGrantsQuery.isLoading ? (_jsx(Skeleton, { className: "h-6 w-full rounded-md" })) : null, _jsx(Tabs, { value: agents.mode, onValueChange: (next) => setAgents(next === 'all' ? ALL_AGENTS : { mode: 'subset', ids: agents.ids }), children: _jsxs(TabsListCompact, { children: [_jsx(TabsTriggerCompact, { value: "all", children: tI18nComplete.raw('text54c32d3e2cfa') }), _jsx(TabsTriggerCompact, { value: "subset", children: tI18nComplete.raw('textb080141d6840') })] }) }), agents.mode === 'all' ? (_jsx(FieldDescription, { children: projectAgents.length === 0
|
|
881
884
|
? tI18nComplete.raw('text7bf9d07dcb99')
|
|
882
|
-
: tI18nComplete('textc5bb9fda9711', { value0: projectAgents.length }) })) : null, agents.mode === 'subset' ? (resourceGrantsQuery.isLoading ? (_jsx(Skeleton, { className: "h-24 w-full rounded-md" })) : projectAgents.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs", children: tI18nComplete.raw('text7bf9d07dcb99') })) : (_jsx("div", { className: "border-border max-h-40 overflow-y-auto rounded-md border p-1", children: projectAgents.map((agent) => (_jsx(Checkbox, { label: agent.name, checked:
|
|
885
|
+
: tI18nComplete('textc5bb9fda9711', { value0: projectAgents.length }) })) : null, agents.mode === 'subset' ? (resourceGrantsQuery.isLoading ? (_jsx(Skeleton, { className: "h-24 w-full rounded-md" })) : projectAgents.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs", children: tI18nComplete.raw('text7bf9d07dcb99') })) : (_jsx("div", { className: "border-border max-h-40 overflow-y-auto rounded-md border p-1", children: projectAgents.map((agent) => (_jsx(Checkbox, { label: agent.name, checked: selectedAgentIdSet.has(agent.id), disabled: pending, onCheckedChange: () => setAgents((prev) => ({
|
|
883
886
|
mode: 'subset',
|
|
884
887
|
ids: prev.ids.includes(agent.id)
|
|
885
888
|
? prev.ids.filter((x) => x !== agent.id)
|
|
@@ -13,7 +13,7 @@ import { errorToast, successToast } from '../../primitives/toast.js';
|
|
|
13
13
|
import { useCopy } from '../../internal/hooks/use-copy.js';
|
|
14
14
|
import { cn } from '../../internal/lib/utils.js';
|
|
15
15
|
import { CheckIcon, CopyIcon } from '@phosphor-icons/react';
|
|
16
|
-
import { AnimatePresence,
|
|
16
|
+
import { AnimatePresence, m } from 'motion/react';
|
|
17
17
|
/**
|
|
18
18
|
* Imperative copy for call sites that are not a `CopyRow` (a kebab item, a
|
|
19
19
|
* toast action button). Same toast pair the three private copies used.
|
|
@@ -37,5 +37,5 @@ export function CopyRow({ value, label, display, successMessage, actions, classN
|
|
|
37
37
|
const copyLabel = copied
|
|
38
38
|
? tI18nComplete.raw('text8d525e5f158b')
|
|
39
39
|
: tI18nComplete.raw('texte21f935f11d7');
|
|
40
|
-
return (_jsxs("div", { className: cn('space-y-1.5', className), children: [label ? _jsx("span", { className: "text-muted-foreground text-xs font-medium", children: label }) : null, _jsxs("div", { className: "bg-popover flex items-center gap-2 rounded-md border px-3 py-2", children: [_jsx("code", { className: "text-foreground min-w-0 flex-1 truncate font-mono text-xs", children: display ?? value }), actions, _jsx(Hint, { label: copyLabel, children: _jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", "aria-label": copyLabel, onClick: () => void copy(value), className: "text-muted-foreground hover:text-foreground shrink-0", children: _jsx("span", { className: "relative inline-flex size-3.5 items-center justify-center", children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsx(
|
|
40
|
+
return (_jsxs("div", { className: cn('space-y-1.5', className), children: [label ? _jsx("span", { className: "text-muted-foreground text-xs font-medium", children: label }) : null, _jsxs("div", { className: "bg-popover flex items-center gap-2 rounded-md border px-3 py-2", children: [_jsx("code", { className: "text-foreground min-w-0 flex-1 truncate font-mono text-xs", children: display ?? value }), actions, _jsx(Hint, { label: copyLabel, children: _jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", "aria-label": copyLabel, onClick: () => void copy(value), className: "text-muted-foreground hover:text-foreground shrink-0", children: _jsx("span", { className: "relative inline-flex size-3.5 items-center justify-center", children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsx(m.span, { initial: { scale: 0.25, opacity: 0, filter: 'blur(4px)' }, animate: { scale: 1, opacity: 1, filter: 'blur(0px)' }, exit: { scale: 0.25, opacity: 0, filter: 'blur(4px)' }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "absolute inset-0 inline-flex items-center justify-center", children: copied ? (_jsx(CheckIcon, { className: "text-kortix-green size-3.5" })) : (_jsx(CopyIcon, { className: "size-3.5" })) }, copied ? 'check' : 'copy') }) }) }) })] })] }));
|
|
41
41
|
}
|
|
@@ -42,6 +42,7 @@ export function SharingPicker({ projectId, value, onChange, copy, showHeading =
|
|
|
42
42
|
const offerMembers = !hideMembers && (accountApiEnabled || value.mode === 'members');
|
|
43
43
|
return (_jsxs("div", { className: "space-y-3", children: [showHeading && _jsx(Label, { children: c.heading }), _jsxs(RadioGroup, { value: value.mode, onValueChange: (v) => onChange({ ...value, mode: v }), className: "space-y-2", children: [_jsx(ShareOption, { value: "project", label: c.project.label, desc: c.project.desc, disabled: isDisabled('project') }), _jsx(ShareOption, { value: "private", label: c.private.label, desc: c.private.desc, disabled: isDisabled('private') }), offerMembers && (_jsx(ShareOption, { value: "members", label: c.members.label, desc: c.members.desc, disabled: isDisabled('members') }))] }), offerMembers && value.mode === 'members' && (_jsx(SubjectPicker, { projectId: projectId, memberIds: value.memberIds, groupIds: value.groupIds, onChange: (memberIds, groupIds) => onChange({ ...value, memberIds, groupIds }) })), hideMembers && !legacyMembers && (_jsxs("p", { className: "text-muted-foreground text-xs leading-relaxed", children: [tI18nComplete.raw('text558b36e065df'), ' ', _jsx("span", { className: "text-foreground/80 font-medium", children: tI18nComplete.raw('textd4f0bc5a29de') }), ' ', tI18nComplete.raw('text3a71ebb2d593')] })), legacyMembers && (_jsx("p", { className: "text-xs leading-relaxed text-amber-600 dark:text-amber-400", children: tI18nComplete.raw('text64f4c4bd39c9') }))] }));
|
|
44
44
|
}
|
|
45
|
+
const NO_INVITE_EMAILS = [];
|
|
45
46
|
/**
|
|
46
47
|
* Legacy adapter over {@link PrincipalPicker}, kept ONLY for the
|
|
47
48
|
* non-access callers of this module (`features/apps/apps-view.tsx` and
|
|
@@ -52,7 +53,7 @@ export function SharingPicker({ projectId, value, onChange, copy, showHeading =
|
|
|
52
53
|
* @deprecated Use `PrincipalPicker` from
|
|
53
54
|
* `@/features/workspace/shared/access`.
|
|
54
55
|
*/
|
|
55
|
-
export function SubjectPicker({ projectId, accountId, memberIds, groupIds, inviteEmails =
|
|
56
|
+
export function SubjectPicker({ projectId, accountId, memberIds, groupIds, inviteEmails = NO_INVITE_EMAILS, onChange, allowInvite = false, kinds = ['member', 'group'], excludeUserIds, emptyLabel = 'No members or groups in this project yet.', allExcludedLabel = 'Everyone is already added.', }) {
|
|
56
57
|
const value = useMemo(() => ({ memberIds, groupIds, inviteEmails }), [memberIds, groupIds, inviteEmails]);
|
|
57
58
|
const scope = projectId
|
|
58
59
|
? { kind: 'project', projectId }
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { isKortixAppUrl } from '../apps/kortix-app-url.js';
|
|
12
12
|
import { APP_ACCESS_PARAM, withoutAppAccessToken } from '../internal/lib/utils/app-access-token.js';
|
|
13
13
|
import { useAppAccess, useProjectApps } from '@kortix/sdk/react';
|
|
14
|
-
import { useRef } from 'react';
|
|
14
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
15
15
|
import { appCan } from '../apps/app-shared.js';
|
|
16
16
|
export { withoutAppAccessToken };
|
|
17
17
|
function hostOf(url) {
|
|
@@ -72,8 +72,10 @@ export function useAppInBrowser(projectId, url) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
const pinned = useRef(null);
|
|
75
|
-
if (url && resolved && pinned.current?.url !== url)
|
|
76
|
-
pinned.current = { url, src: resolved };
|
|
77
75
|
const src = url && pinned.current?.url === url ? pinned.current.src : resolved;
|
|
76
|
+
useLayoutEffect(() => {
|
|
77
|
+
if (url && src && pinned.current?.url !== url)
|
|
78
|
+
pinned.current = { url, src };
|
|
79
|
+
});
|
|
78
80
|
return { app, apps: apps.data ?? [], src };
|
|
79
81
|
}
|
|
@@ -23,7 +23,7 @@ import { buildWebProxyUrl, isExternalUrl, isWebProxyUrl, normalizeExternalInput,
|
|
|
23
23
|
import { useBrowserRecentsStore } from '../internal/stores/browser-recents-store.js';
|
|
24
24
|
import { useTabStore } from '../internal/stores/tab-store.js';
|
|
25
25
|
import { WarningIcon as AlertTriangle, ArrowSquareOutIcon, GlobeIcon as Globe, LinkSimpleIcon as Link2, DotsThreeIcon as MoreHorizontal, ArrowClockwiseIcon as RefreshCw, GearSixIcon as Settings2, } from '@phosphor-icons/react';
|
|
26
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
26
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
27
27
|
import { BrowserLanding } from './browser-landing.js';
|
|
28
28
|
import { PreviewLoadingOverlay, SandboxAddressBar } from './sandbox-browser-chrome.js';
|
|
29
29
|
function normalizePreviewLabel(value, fallback) {
|
|
@@ -34,6 +34,7 @@ function normalizePreviewLabel(value, fallback) {
|
|
|
34
34
|
return fallback;
|
|
35
35
|
return trimmed;
|
|
36
36
|
}
|
|
37
|
+
const subscribeNoop = () => () => { };
|
|
37
38
|
/**
|
|
38
39
|
* Preview tab content — renders a proxied sandbox URL in an iframe
|
|
39
40
|
* with a browser-like toolbar: editable address bar, refresh, back/forward, open externally.
|
|
@@ -56,8 +57,7 @@ export function BrowserPanel({ tabId, projectId, projectSessionId }) {
|
|
|
56
57
|
const loadTimeoutRef = useRef(null);
|
|
57
58
|
// Recents come from a persisted store — render them only after mount so the
|
|
58
59
|
// server and first client render agree.
|
|
59
|
-
const
|
|
60
|
-
useEffect(() => setMounted(true), []);
|
|
60
|
+
const mounted = useSyncExternalStore(subscribeNoop, () => true, () => false);
|
|
61
61
|
// Extract metadata from tab
|
|
62
62
|
const rawPreviewUrl = tab?.metadata?.url || '';
|
|
63
63
|
const port = tab?.metadata?.port || 0;
|
|
@@ -199,7 +199,7 @@ export function BrowserPanel({ tabId, projectId, projectSessionId }) {
|
|
|
199
199
|
});
|
|
200
200
|
pushHistory(newProxyUrl);
|
|
201
201
|
handleRefresh();
|
|
202
|
-
}, [subdomainOpts, rewritePortPath, tabId, updateTabMetadata, pushHistory, handleRefresh]);
|
|
202
|
+
}, [subdomainOpts, rewritePortPath, tabId, appPreviewTitle, updateTabMetadata, pushHistory, handleRefresh]);
|
|
203
203
|
const canGoBack = historyIndex > 0;
|
|
204
204
|
const canGoForward = historyIndex < history.length - 1;
|
|
205
205
|
/** Walk the history stack in either direction; the two directions were two
|
|
@@ -248,7 +248,7 @@ export function BrowserPanel({ tabId, projectId, projectSessionId }) {
|
|
|
248
248
|
handleRefresh();
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
}, [canGoBack, canGoForward, historyIndex, history, tabId, updateTabMetadata, handleRefresh]);
|
|
251
|
+
}, [canGoBack, canGoForward, historyIndex, history, tabId, appPreviewTitle, updateTabMetadata, handleRefresh]);
|
|
252
252
|
/** Home: the landing (Apps + recents). An empty url is what renders it. */
|
|
253
253
|
const handleHome = useCallback(() => {
|
|
254
254
|
updateTabMetadata({
|
|
@@ -290,7 +290,7 @@ export function BrowserPanel({ tabId, projectId, projectSessionId }) {
|
|
|
290
290
|
path,
|
|
291
291
|
},
|
|
292
292
|
};
|
|
293
|
-
}, [fullUrl, isExternalBrowsing, originalUrl, port, tab?.metadata?.path, tab?.title]);
|
|
293
|
+
}, [fullUrl, isExternalBrowsing, originalUrl, port, tab?.metadata?.path, tab?.title, appPreviewTitle]);
|
|
294
294
|
const hasPreview = !!previewUrl || hostPreview.failed;
|
|
295
295
|
const emptyState = (_jsx(EmptyState, { icon: Globe, className: "h-full", title: tHardcodedUi.raw('autoComponentsTabsPreviewTabContentJsxTextPreviewBrowser8136da05'), description: _jsxs(_Fragment, { children: [tHardcodedUi.raw('autoComponentsTabsPreviewTabContentJsxTextOpenAnAppda305669'), ' ', _jsx("span", { className: "text-foreground/80 font-mono", children: "3000" }), ' ', tHardcodedUi.raw('autoComponentsTabsPreviewTabContentJsxTextIfYouKnow6745fa88')] }) }));
|
|
296
296
|
// Copy-public-link action, surfaced from the "⋯" overflow menu.
|
|
@@ -64,6 +64,9 @@ export declare function SandboxAddressBar({ displayValue, hasPreview, isLoading,
|
|
|
64
64
|
* Cross-origin iframes frequently never fire onLoad, so neither surface may
|
|
65
65
|
* trust the event alone — the parent decides when to show and dismiss this.
|
|
66
66
|
*/
|
|
67
|
-
export declare function PreviewLoadingOverlay({ label }: {
|
|
67
|
+
export declare function PreviewLoadingOverlay({ label, detail, action, }: {
|
|
68
68
|
label: string;
|
|
69
|
+
/** A second line once the load is slow: why, and the way out. */
|
|
70
|
+
detail?: string;
|
|
71
|
+
action?: React.ReactNode;
|
|
69
72
|
}): import("react").JSX.Element;
|
|
@@ -80,6 +80,7 @@ export function parseAddressInput(value) {
|
|
|
80
80
|
return null;
|
|
81
81
|
return toInternalUrl(parsed.port, parsed.path);
|
|
82
82
|
}
|
|
83
|
+
const NO_SUGGESTIONS = [];
|
|
83
84
|
/**
|
|
84
85
|
* The back / forward buttons and the home / address / reload group every
|
|
85
86
|
* sandbox preview surface renders, with the hostname highlighted at rest. The bar
|
|
@@ -88,7 +89,7 @@ export function parseAddressInput(value) {
|
|
|
88
89
|
* controls around it (`BrowserPanel`'s overflow menu, `AppPreview`'s viewer
|
|
89
90
|
* actions and close) — the component is a fragment, so it slots between them.
|
|
90
91
|
*/
|
|
91
|
-
export function SandboxAddressBar({ displayValue, hasPreview, isLoading, canGoBack, canGoForward, onBack, onForward, onReload, onHome, onNavigate, suggestions =
|
|
92
|
+
export function SandboxAddressBar({ displayValue, hasPreview, isLoading, canGoBack, canGoForward, onBack, onForward, onReload, onHome, onNavigate, suggestions = NO_SUGGESTIONS, placeholder, ariaLabel, inputRef, resetOnEscape = true, trailing, }) {
|
|
92
93
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
93
94
|
const [addressValue, setAddressValue] = useState(displayValue);
|
|
94
95
|
const [isEditing, setIsEditing] = useState(false);
|
|
@@ -144,6 +145,6 @@ export function SandboxAddressBar({ displayValue, hasPreview, isLoading, canGoBa
|
|
|
144
145
|
* Cross-origin iframes frequently never fire onLoad, so neither surface may
|
|
145
146
|
* trust the event alone — the parent decides when to show and dismiss this.
|
|
146
147
|
*/
|
|
147
|
-
export function PreviewLoadingOverlay({ label }) {
|
|
148
|
-
return (_jsx("div", { className: "bg-background/80 absolute inset-0 z-10 flex items-center justify-center", children: _jsxs("div", { className: "text-muted-foreground flex flex-col items-center gap-2", children: [_jsx(Loading, { className: "size-4" }), _jsx("p", { className: "text-xs", children: label })] }) }));
|
|
148
|
+
export function PreviewLoadingOverlay({ label, detail, action, }) {
|
|
149
|
+
return (_jsx("div", { className: "bg-background/80 absolute inset-0 z-10 flex items-center justify-center", children: _jsxs("div", { className: "text-muted-foreground flex max-w-xs flex-col items-center gap-2 px-4 text-center", children: [_jsx(Loading, { className: "size-4" }), _jsx("p", { className: "text-xs", children: label }), detail && _jsx("p", { className: "text-xs text-pretty", children: detail }), action] }) }));
|
|
149
150
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ProjectConfigSummary } from '@kortix/sdk';
|
|
2
|
+
type AgentScope = NonNullable<ProjectConfigSummary['agents'][number]['scope']>;
|
|
3
|
+
/** One grant on the card: everything (`'all'`) or how many are listed. */
|
|
4
|
+
export interface AgentAccessPart {
|
|
5
|
+
kind: 'connectors' | 'secrets' | 'actions';
|
|
6
|
+
value: 'all' | number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* What an agent's configuration grants, for its card. The card used to show
|
|
10
|
+
* only the agent's own description, and the starter's says "full tool access"
|
|
11
|
+
* whatever the configuration grants (KRTX-3025). Null for an agent that
|
|
12
|
+
* kortix.yaml does not govern: there is nothing to summarize.
|
|
13
|
+
*/
|
|
14
|
+
export declare function agentAccess(scope: AgentScope | undefined): {
|
|
15
|
+
kind: 'full';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'scoped';
|
|
18
|
+
parts: AgentAccessPart[];
|
|
19
|
+
} | null;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an agent's configuration grants, for its card. The card used to show
|
|
3
|
+
* only the agent's own description, and the starter's says "full tool access"
|
|
4
|
+
* whatever the configuration grants (KRTX-3025). Null for an agent that
|
|
5
|
+
* kortix.yaml does not govern: there is nothing to summarize.
|
|
6
|
+
*/
|
|
7
|
+
export function agentAccess(scope) {
|
|
8
|
+
if (!scope)
|
|
9
|
+
return null;
|
|
10
|
+
const grant = (value) => value === 'all' ? 'all' : (value?.length ?? 0);
|
|
11
|
+
const parts = [
|
|
12
|
+
{ kind: 'connectors', value: grant(scope.connectors) },
|
|
13
|
+
{ kind: 'secrets', value: grant(scope.env) },
|
|
14
|
+
{ kind: 'actions', value: grant(scope.kortix_permissions ?? scope.kortix_cli) },
|
|
15
|
+
];
|
|
16
|
+
return parts.every((part) => part.value === 'all') ? { kind: 'full' } : { kind: 'scoped', parts };
|
|
17
|
+
}
|
|
@@ -39,7 +39,7 @@ import { listConnectors, listProjectSecrets, } from '@kortix/sdk';
|
|
|
39
39
|
import { contract, qk, useProjectAccountId, useProjectApps, } from '@kortix/sdk/react';
|
|
40
40
|
import { GlobeIcon, KeyIcon, PlusIcon, SparkleIcon } from '@phosphor-icons/react';
|
|
41
41
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
42
|
-
import {
|
|
42
|
+
import { useState } from 'react';
|
|
43
43
|
import { appGrantRows } from './agent-apps-grant.js';
|
|
44
44
|
/**
|
|
45
45
|
* All · Pick · None over a grid of selectable cards. `items` is the project's
|
|
@@ -110,18 +110,17 @@ export function ConnectorsGrantPage({ projectId, editor, }) {
|
|
|
110
110
|
record: connectors.find((c) => c.slug === detailSlug),
|
|
111
111
|
isSuccess: connectorsQuery.isSuccess,
|
|
112
112
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
setDetailSlug(null);
|
|
116
|
-
}, [detail.isMissing]);
|
|
113
|
+
if (detail.isMissing)
|
|
114
|
+
setDetailSlug(null);
|
|
117
115
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: qk.project.connectors(projectId) });
|
|
118
116
|
const { draft, set } = editor;
|
|
119
117
|
return (_jsxs(_Fragment, { children: [_jsx(EditorSection, { title: tI18nComplete.raw('textc3d2e79ebdd0'), description: tI18nComplete.raw('textc174fc077197'), trailing: _jsx(GrantHeaderTrailing, { value: draft.connectors, tab: "connectors", label: tI18nComplete.raw('textd83250185d41') }), children: _jsxs("div", { className: "space-y-2 py-4", children: [_jsx(GrantCatalog, { value: draft.connectors, onChange: (v) => {
|
|
120
118
|
set('connectors', v);
|
|
121
119
|
const required = draft.connectors_required ?? [];
|
|
120
|
+
const picked = new Set(Array.isArray(v) ? v : []);
|
|
122
121
|
const kept = v === 'all' || v === 'none' || !Array.isArray(v)
|
|
123
122
|
? []
|
|
124
|
-
: required.filter((slug) =>
|
|
123
|
+
: required.filter((slug) => picked.has(slug));
|
|
125
124
|
if (kept.length !== required.length) {
|
|
126
125
|
set('connectors_required', kept.length ? kept : undefined);
|
|
127
126
|
}
|
|
@@ -166,7 +166,8 @@ function AgentPageFrame({ header, section, sections, sectionHref, pane, footer,
|
|
|
166
166
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
167
167
|
const isMobile = useIsMobile();
|
|
168
168
|
const localizedConfig = useLocalizedAgentConfigCatalog();
|
|
169
|
-
const
|
|
169
|
+
const sectionKeys = new Set(sections);
|
|
170
|
+
const items = localizedConfig.sections.filter((s) => sectionKeys.has(s.key));
|
|
170
171
|
// The Preferences rail's rule: a heading over a lone group labels nothing.
|
|
171
172
|
const groups = localizedConfig.groups
|
|
172
173
|
.map((group) => ({
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { customizePath, uiRoutes } from '../../lib/routes.js';
|
|
4
4
|
import { NewEntityMenu } from '../shared/new-entity-menu.js';
|
|
5
5
|
import { useTranslations as useI18nTranslations } from '../../i18n/index.js';
|
|
6
|
+
import { agentAccess } from './agent-access.js';
|
|
6
7
|
import { useMemo, useRef, useState } from 'react';
|
|
7
8
|
import { Badge } from '../../primitives/badge.js';
|
|
8
9
|
import { Button } from '../../primitives/button.js';
|
|
@@ -163,10 +164,26 @@ function CardCaret() {
|
|
|
163
164
|
function AgentCardFacts({ agent, triggerCount, peopleCount, }) {
|
|
164
165
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
165
166
|
const model = agent.model ? agent.model.split('/').pop() : null;
|
|
167
|
+
const accessLabel = accessLabelOf(agentAccess(agent.scope), tI18nComplete);
|
|
166
168
|
const sep = (_jsx("span", { "aria-hidden": true, className: "text-muted-foreground", children: "\u00B7" }));
|
|
167
169
|
return (_jsxs(_Fragment, { children: [_jsx("span", { className: cn('truncate', model && 'font-mono'), children: model ?? tI18nComplete.raw('text3840d9d29421') }), sep, _jsxs("span", { className: "tabular-nums", children: [triggerCount, " ", triggerCount === 1 ? 'trigger' : 'triggers'] }), peopleCount !== null ? (_jsxs(_Fragment, { children: [sep, _jsx("span", { className: "tabular-nums", children: peopleCount === 0
|
|
168
170
|
? tI18nComplete.raw('textb6c70926c1aa')
|
|
169
|
-
: `${peopleCount} ${peopleCount === 1 ? 'grant' : 'grants'}` })] })) : null] }));
|
|
171
|
+
: `${peopleCount} ${peopleCount === 1 ? 'grant' : 'grants'}` })] })) : null, accessLabel ? _jsx("span", { className: "basis-full tabular-nums", children: accessLabel }) : null] }));
|
|
172
|
+
}
|
|
173
|
+
/** The access slot: what the configuration grants (KRTX-3025). */
|
|
174
|
+
function accessLabelOf(access, t) {
|
|
175
|
+
if (!access)
|
|
176
|
+
return null;
|
|
177
|
+
if (access.kind === 'full')
|
|
178
|
+
return t.raw('textf19611c61ca5');
|
|
179
|
+
const LABELS = {
|
|
180
|
+
connectors: { all: 'textd83250185d41', count: 'text15589a542412' },
|
|
181
|
+
secrets: { all: 'text0b38e3daeb93', count: 'texte96d99ad9896' },
|
|
182
|
+
actions: { all: 'text70b8d7e10749', count: 'text2d3f4433505a' },
|
|
183
|
+
};
|
|
184
|
+
return access.parts
|
|
185
|
+
.map(({ kind, value }) => value === 'all' ? t.raw(LABELS[kind].all) : t(LABELS[kind].count, { count: value }))
|
|
186
|
+
.join(', ');
|
|
170
187
|
}
|
|
171
188
|
/**
|
|
172
189
|
* Card badges — "also a subagent", the default-agent star, disabled.
|
|
@@ -25,8 +25,9 @@ export function mcpServerIdentity(rawUrl, takenSlugs) {
|
|
|
25
25
|
labels.pop();
|
|
26
26
|
const base = [...labels].reverse().find((label) => !GENERIC_LABELS.has(label)) ?? labels[labels.length - 1] ?? 'mcp';
|
|
27
27
|
const root = normalizeConnectorConnectionSlug(base) || 'mcp';
|
|
28
|
+
const taken = new Set(takenSlugs);
|
|
28
29
|
let slug = root;
|
|
29
|
-
for (let n = 2;
|
|
30
|
+
for (let n = 2; taken.has(slug); n++)
|
|
30
31
|
slug = `${root}-${n}`;
|
|
31
32
|
const name = root.charAt(0).toUpperCase() + root.slice(1);
|
|
32
33
|
return { slug, name };
|
|
@@ -37,8 +38,9 @@ export function mcpServerIdentity(rawUrl, takenSlugs) {
|
|
|
37
38
|
*/
|
|
38
39
|
export function mcpAppIdentity(appName, takenSlugs) {
|
|
39
40
|
const root = normalizeConnectorConnectionSlug(appName) || 'mcp';
|
|
41
|
+
const taken = new Set(takenSlugs);
|
|
40
42
|
let slug = root;
|
|
41
|
-
for (let n = 2;
|
|
43
|
+
for (let n = 2; taken.has(slug); n++)
|
|
42
44
|
slug = `${root}-${n}`;
|
|
43
45
|
return { slug, name: appName };
|
|
44
46
|
}
|
|
@@ -18,7 +18,9 @@ export type AppRef = {
|
|
|
18
18
|
slug: string;
|
|
19
19
|
};
|
|
20
20
|
export declare function connectorsHref(projectId: string, scope?: 'all' | 'connected' | 'channels'): string;
|
|
21
|
-
|
|
21
|
+
export declare function connectorSearchFromParams(search: Pick<URLSearchParams, 'get'> | null | undefined): string;
|
|
22
|
+
/** Sets `?q=` to the trimmed query, or removes it when the query is empty. */
|
|
23
|
+
export declare function withConnectorSearch(params: URLSearchParams, query: string): URLSearchParams;
|
|
22
24
|
export declare function appRefFromEntry(entry: CatalogEntry): AppRef | null;
|
|
23
25
|
export declare function appHref(projectId: string, app: AppRef): string;
|
|
24
26
|
export declare function appRefFromLocation(appSegment: string, search: URLSearchParams | null): AppRef | null;
|
|
@@ -23,6 +23,24 @@ export function connectorsHref(projectId, scope) {
|
|
|
23
23
|
return scope ? `${base(projectId)}?scope=${scope}` : base(projectId);
|
|
24
24
|
}
|
|
25
25
|
/** `null` for the computer entry: a computer has no app page. */
|
|
26
|
+
/**
|
|
27
|
+
* The catalogue search, held in `?q=`. Opening a connector is a route change
|
|
28
|
+
* that unmounts the catalogue; a search kept only in state was gone on browser
|
|
29
|
+
* Back (KRTX-3018).
|
|
30
|
+
*/
|
|
31
|
+
const SEARCH_PARAM = 'q';
|
|
32
|
+
export function connectorSearchFromParams(search) {
|
|
33
|
+
return search?.get(SEARCH_PARAM)?.trim() ?? '';
|
|
34
|
+
}
|
|
35
|
+
/** Sets `?q=` to the trimmed query, or removes it when the query is empty. */
|
|
36
|
+
export function withConnectorSearch(params, query) {
|
|
37
|
+
const trimmed = query.trim();
|
|
38
|
+
if (trimmed)
|
|
39
|
+
params.set(SEARCH_PARAM, trimmed);
|
|
40
|
+
else
|
|
41
|
+
params.delete(SEARCH_PARAM);
|
|
42
|
+
return params;
|
|
43
|
+
}
|
|
26
44
|
export function appRefFromEntry(entry) {
|
|
27
45
|
if (entry.source === 'discover') {
|
|
28
46
|
return { source: 'discover', slug: entry.slug, id: entry.connector.id };
|
|
@@ -8,7 +8,8 @@ import { contract, FRESHNESS, qk, useFeatureFlag, useProjectAccountId } from '@k
|
|
|
8
8
|
import { MagnifyingGlassIcon, PlugIcon, PlugsConnectedIcon } from '@phosphor-icons/react';
|
|
9
9
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
10
10
|
import { lazyClient } from '../../lib/lazy-client.js';
|
|
11
|
-
import { useUiPathname, useUiRouter, useUiSearchParams } from '../../providers/ui-provider.js';
|
|
11
|
+
import { useUiPathname, useUiRouter, useUiSearchParams, useUiShallowNavigate, } from '../../providers/ui-provider.js';
|
|
12
|
+
import { useDebounce } from '../../hooks/use-debounce.js';
|
|
12
13
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
13
14
|
import { PoliciesPanel } from '../../workspace/projects/policies-panel.js';
|
|
14
15
|
import { Button } from '../../primitives/button.js';
|
|
@@ -37,7 +38,7 @@ import { useTunnelConnections } from '@kortix/sdk/react';
|
|
|
37
38
|
import { useSlashFocus } from '../../internal/hooks/index.js';
|
|
38
39
|
import { connectorDisplayName, connectorSummary, filterConnectors, } from './connector-filter.js';
|
|
39
40
|
import { ConnectorMovesBanner } from './connector-moves-banner.js';
|
|
40
|
-
import { appHref, appRefFromEntry, connectorHref, legacyDetailRedirect } from './connector-routes.js';
|
|
41
|
+
import { appHref, appRefFromEntry, connectorHref, connectorSearchFromParams, legacyDetailRedirect, withConnectorSearch, } from './connector-routes.js';
|
|
41
42
|
import { useInstall } from './install/use-install.js';
|
|
42
43
|
/**
|
|
43
44
|
* The click-gated Add form, split out of this route's initial chunk.
|
|
@@ -172,7 +173,11 @@ export function ConnectorsPage({ projectId }) {
|
|
|
172
173
|
const canWrite = useProjectCan(projectId, PROJECT_ACTIONS.PROJECT_CONNECTOR_WRITE, { accountId }).allowed ===
|
|
173
174
|
true;
|
|
174
175
|
const queryClient = useQueryClient();
|
|
175
|
-
const
|
|
176
|
+
const { search, router, pathname, replaceParams, setScope, rulesOpen, setRulesOpen } = useConnectorNavigation(projectId);
|
|
177
|
+
// The search lives in `?q=`: opening a connector unmounts this page, and
|
|
178
|
+
// browser Back must bring the same results back (KRTX-3018).
|
|
179
|
+
const [query, setQuery] = useState(() => connectorSearchFromParams(search));
|
|
180
|
+
useSearchInUrl(query, search, pathname);
|
|
176
181
|
const [panel, setPanel] = useState(null);
|
|
177
182
|
// The catalogue app whose own MCP server the MCP dialog connects. Null for
|
|
178
183
|
// a server the user pastes (New → MCP server).
|
|
@@ -180,7 +185,6 @@ export function ConnectorsPage({ projectId }) {
|
|
|
180
185
|
const [computerOpen, setComputerOpen] = useState(false);
|
|
181
186
|
const tSharing = useI18nTranslations('accessSharing');
|
|
182
187
|
const tPages = useI18nTranslations('connectorPages');
|
|
183
|
-
const { search, router, pathname, replaceParams, setScope, rulesOpen, setRulesOpen } = useConnectorNavigation(projectId);
|
|
184
188
|
const connectorsQuery = useQuery({
|
|
185
189
|
queryKey: qk.project.connectors(projectId),
|
|
186
190
|
queryFn: () => listConnectors(projectId, { includeSchemas: false }),
|
|
@@ -374,6 +378,21 @@ export function ConnectorsPage({ projectId }) {
|
|
|
374
378
|
router.push(connectorHref(projectId, slug, { addAccount }));
|
|
375
379
|
} }) })] }) })] }) }), _jsxs(SplitSheetContent, { children: [_jsxs(SplitSheetHeader, { children: [_jsx(SplitSheetTitle, { children: tI18nComplete.raw('text1d59a5e09714') }), _jsx(SplitSheetDescription, { children: tI18nComplete.raw('text014d10bd3c64') })] }), _jsx(SplitSheetBody, { children: _jsx(PoliciesPanel, { projectId: projectId }) })] })] }));
|
|
376
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Mirrors the settled search into `?q=` with a shallow replace: no history
|
|
383
|
+
* entry per keystroke, and no server round trip for a view already on screen.
|
|
384
|
+
*/
|
|
385
|
+
function useSearchInUrl(query, search, pathname) {
|
|
386
|
+
const shallowNavigate = useUiShallowNavigate();
|
|
387
|
+
const { debouncedValue } = useDebounce(query.trim(), 300);
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
if (connectorSearchFromParams(search) === debouncedValue)
|
|
390
|
+
return;
|
|
391
|
+
const params = withConnectorSearch(new URLSearchParams(search?.toString() ?? ''), debouncedValue);
|
|
392
|
+
const suffix = params.toString();
|
|
393
|
+
shallowNavigate(suffix ? `${pathname}?${suffix}` : pathname, { replace: true });
|
|
394
|
+
}, [debouncedValue, search, pathname, shallowNavigate]);
|
|
395
|
+
}
|
|
377
396
|
function useConnectorNavigation(projectId) {
|
|
378
397
|
const search = useUiSearchParams();
|
|
379
398
|
const router = useUiRouter();
|
|
@@ -49,13 +49,11 @@ export function ConnectorAdvancedTools({ connector, advancedRules, toolRules, lo
|
|
|
49
49
|
// re-fired whenever a rule was added or removed, so the panel sprang back open
|
|
50
50
|
// moments after the user collapsed it, which read as the disclosure opening by
|
|
51
51
|
// itself. After the first reveal the panel is the user's to control.
|
|
52
|
-
const revealedExistingRules =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return;
|
|
56
|
-
revealedExistingRules.current = true;
|
|
52
|
+
const [revealedExistingRules, setRevealedExistingRules] = useState(false);
|
|
53
|
+
if (!revealedExistingRules && advancedRules.length > 0) {
|
|
54
|
+
setRevealedExistingRules(true);
|
|
57
55
|
setAdvancedOpen(true);
|
|
58
|
-
}
|
|
56
|
+
}
|
|
59
57
|
const savePatternRules = () => onSave([...toolRules, ...draftToRules(draft)]);
|
|
60
58
|
return (_jsx(_Fragment, { children: hasTools || advancedRules.length > 0 ? (_jsxs(Disclosure, { variant: "outline", className: "overflow-hidden", open: advancedOpen, onOpenChange: setAdvancedOpen, children: [_jsx(DisclosureTrigger, { variant: "outline", children: _jsxs(Button, { variant: "popover", children: [tI18nComplete.raw('text9f088dbebd6c'), advancedRules.length > 0 ? (_jsx(Badge, { variant: "secondary", size: "sm", children: advancedRules.length })) : null, _jsx("span", { className: "text-muted-foreground ml-auto text-xs font-normal", children: tI18nComplete.raw('text59da2efb2423') })] }) }), _jsx(DisclosureContent, { variant: "outline", contentClassName: "border-border border-t", children: _jsxs("div", { className: "space-y-5 px-4 py-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-foreground text-sm font-medium", children: tI18nComplete.raw('text594bdd4c19b2') }), _jsx("p", { className: "text-muted-foreground mt-0.5 text-xs text-pretty", children: tI18nComplete.raw('textf900377c2048') })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [sensitivePending ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, _jsx(Switch, { checked: connector.sensitive === true, onCheckedChange: onSensitive, disabled: frozen || sensitivePending, "aria-label": tI18nComplete.raw('text594bdd4c19b2') })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: tI18nComplete.raw('text380c0b05bcfc') }), _jsxs("p", { className: "text-muted-foreground text-xs text-pretty", children: [tI18nComplete.raw('textde00adeaf21b'), _jsx("code", { className: "font-mono", children: "delete_*" }), tI18nComplete.raw('text3a13855164e7'), _jsx("code", { className: "font-mono", children: "/^send/i" }), tI18nComplete.raw('texte45481e75a5e')] }), draft.map((row) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { value: row.match, placeholder: tI18nComplete.raw('text74ad536dca05'), variant: "popover", size: "xs", className: "flex-1 font-mono", "aria-label": tI18nComplete.raw('text5c9c672f4cd3'), disabled: frozen, onChange: (event) => setDraft((rows) => rows.map((candidate) => candidate.id === row.id
|
|
61
59
|
? { ...candidate, match: event.target.value }
|
|
@@ -217,6 +217,7 @@ export function ProviderAccordion({ projectId, canWrite = false, enabled = true,
|
|
|
217
217
|
connectedIds,
|
|
218
218
|
search,
|
|
219
219
|
});
|
|
220
|
+
const disabledProviders = new Set(access.data?.enforced === true ? access.data.disabledProviders : []);
|
|
220
221
|
const rows = ordered.map((entry) => {
|
|
221
222
|
const method = providerMethod(entry);
|
|
222
223
|
return {
|
|
@@ -233,7 +234,7 @@ export function ProviderAccordion({ projectId, canWrite = false, enabled = true,
|
|
|
233
234
|
? 'connected'
|
|
234
235
|
: 'none',
|
|
235
236
|
via: connectedVia(entry.id, connectedIds),
|
|
236
|
-
disabled:
|
|
237
|
+
disabled: disabledProviders.has(entry.id),
|
|
237
238
|
};
|
|
238
239
|
});
|
|
239
240
|
return {
|
|
@@ -235,6 +235,7 @@ export function ProviderConnect({ projectId, canWrite = false, enabled = true, s
|
|
|
235
235
|
connectedIds,
|
|
236
236
|
search,
|
|
237
237
|
});
|
|
238
|
+
const disabledProviders = new Set(access.data?.enforced === true ? access.data.disabledProviders : []);
|
|
238
239
|
const rows = ordered.map((entry) => {
|
|
239
240
|
const method = providerMethod(entry);
|
|
240
241
|
return {
|
|
@@ -252,7 +253,7 @@ export function ProviderConnect({ projectId, canWrite = false, enabled = true, s
|
|
|
252
253
|
? 'connected'
|
|
253
254
|
: 'none',
|
|
254
255
|
via: connectedVia(entry.id, connectedIds),
|
|
255
|
-
disabled:
|
|
256
|
+
disabled: disabledProviders.has(entry.id),
|
|
256
257
|
};
|
|
257
258
|
});
|
|
258
259
|
return {
|
|
@@ -56,7 +56,7 @@ function TelegramConnectForm({ projectId, onDone }) {
|
|
|
56
56
|
function TelegramUsersForm({ projectId, initial, onDone, }) {
|
|
57
57
|
const t = useTranslations('hardcodedUi.i18nComplete');
|
|
58
58
|
const save = useSetTelegramAllowedUsers();
|
|
59
|
-
const [users, setUsers] = useState(initial.join(', '));
|
|
59
|
+
const [users, setUsers] = useState(() => initial.join(', '));
|
|
60
60
|
const ids = parseTelegramUserIds(users);
|
|
61
61
|
return (_jsxs("form", { className: "space-y-4", onSubmit: (event) => {
|
|
62
62
|
event.preventDefault();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslations as useI18nTranslations } from '../../i18n/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { useState } from 'react';
|
|
5
5
|
import { Button } from '../../primitives/button.js';
|
|
6
6
|
import { Disclosure, DisclosureContent, DisclosureTrigger } from '../../primitives/disclosure.js';
|
|
7
7
|
import { Field, FieldDescription, FieldGroup, FieldLabel } from '../../primitives/field.js';
|
|
@@ -35,14 +35,16 @@ export function ConnectorConnectionModal({ open, idPrefix, title, initialName, i
|
|
|
35
35
|
const [slug, setSlug] = useState(initialSlug);
|
|
36
36
|
const [slugEdited, setSlugEdited] = useState(false);
|
|
37
37
|
const [optionsOpen, setOptionsOpen] = useState(false);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
const [resetFor, setResetFor] = useState(null);
|
|
39
|
+
if (!resetFor || resetFor[0] !== proposedName || resetFor[1] !== initialSlug || resetFor[2] !== open) {
|
|
40
|
+
setResetFor([proposedName, initialSlug, open]);
|
|
41
|
+
if (open) {
|
|
42
|
+
setName(proposedName);
|
|
43
|
+
setSlug(initialSlug);
|
|
44
|
+
setSlugEdited(false);
|
|
45
|
+
setOptionsOpen(false);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
46
48
|
const slugAvailable = isConnectorConnectionSlugAvailable(slug, existingSlugs);
|
|
47
49
|
const slugInvalid = slug.length > 0 && !slugAvailable;
|
|
48
50
|
const slugDescriptionId = `${idPrefix}-slug-description`;
|
|
@@ -421,14 +421,16 @@ function EmailSenderPolicyEditor({ projectId, connectorSlug, policy, canWrite =
|
|
|
421
421
|
const [domains, setDomains] = useState(() => initial.allowedDomains.join('\n'));
|
|
422
422
|
const [regex, setRegex] = useState(initial.allowedRegex ?? '');
|
|
423
423
|
const [error, setError] = useState(null);
|
|
424
|
-
|
|
424
|
+
const [syncedPolicy, setSyncedPolicy] = useState(policy);
|
|
425
|
+
if (policy !== syncedPolicy) {
|
|
426
|
+
setSyncedPolicy(policy);
|
|
425
427
|
const next = normalizeEmailSenderPolicy(policy);
|
|
426
428
|
setRestricted(next.mode === 'restricted');
|
|
427
429
|
setEmails(next.allowedEmails.join('\n'));
|
|
428
430
|
setDomains(next.allowedDomains.join('\n'));
|
|
429
431
|
setRegex(next.allowedRegex ?? '');
|
|
430
432
|
setError(null);
|
|
431
|
-
}
|
|
433
|
+
}
|
|
432
434
|
const nextPolicy = () => ({
|
|
433
435
|
mode: restricted ? 'restricted' : 'allow_all',
|
|
434
436
|
allowedEmails: splitPolicyList(emails),
|
|
@@ -948,17 +950,13 @@ export function CustomConnectorForm({ projectId, emailChannelEnabled, onAdded, }
|
|
|
948
950
|
const timer = window.setTimeout(() => setDiscoveryDraft(draft), 400);
|
|
949
951
|
return () => window.clearTimeout(timer);
|
|
950
952
|
}, [draft]);
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (draft.provider === 'channel' && oauth2Selected) {
|
|
959
|
-
setOauth2Selected(false);
|
|
960
|
-
}
|
|
961
|
-
}, [draft.provider, oauth2Selected]);
|
|
953
|
+
if (!emailChannelEnabled && draft.provider === 'channel' && draft.platform === 'email') {
|
|
954
|
+
setDraft((current) => ({ ...current, platform: 'slack' }));
|
|
955
|
+
}
|
|
956
|
+
// Channel connectors have no OAuth2-at-creation offer.
|
|
957
|
+
if (draft.provider === 'channel' && oauth2Selected) {
|
|
958
|
+
setOauth2Selected(false);
|
|
959
|
+
}
|
|
962
960
|
const save = useCreateCustomConnector(projectId, onAdded);
|
|
963
961
|
const discovery = useQuery({
|
|
964
962
|
queryKey: ['connector-auth-discovery', projectId, discoveryDraft],
|