@kortix/ui 0.14.6 → 0.14.8
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 +490 -382
- package/dist/_vendor/shared/runtime-versions.d.ts +4 -0
- package/dist/access/access/access-dialog-share.js +4 -7
- package/dist/access/access/access-dialog.js +2 -1
- package/dist/access/access/principal-picker.js +6 -5
- package/dist/customize/agents/agent-authority-card.js +1 -2
- package/dist/customize/agents/agent-cover-card.d.ts +17 -0
- package/dist/customize/agents/agent-cover-card.js +13 -0
- package/dist/customize/agents/agent-detail-aside.js +15 -14
- package/dist/customize/agents/agent-drives-section.js +5 -6
- package/dist/customize/agents/agent-grant-pages.js +68 -70
- package/dist/customize/agents/agent-page.js +27 -19
- package/dist/customize/agents/agent-people-section.js +18 -17
- package/dist/customize/agents/agent-triggers-section.js +6 -5
- package/dist/customize/agents/agents-page.js +43 -49
- package/dist/customize/connectors/catalog/catalog-entry.d.ts +14 -0
- package/dist/customize/connectors/catalog/catalog-entry.js +18 -1
- package/dist/customize/connectors/catalog/connector-browse.d.ts +28 -0
- package/dist/customize/connectors/catalog/connector-browse.js +18 -5
- package/dist/customize/connectors/detail/app-page.js +19 -5
- package/dist/customize/connectors/detail/connector-modal.d.ts +10 -13
- package/dist/customize/connectors/detail/connector-modal.js +18 -101
- package/dist/customize/connectors/detail/connector-notice.js +6 -2
- package/dist/customize/connectors/detail/connector-page.d.ts +30 -0
- package/dist/customize/connectors/detail/connector-page.js +26 -13
- package/dist/customize/migrate-to-v2/migration-prompt.d.ts +1 -0
- package/dist/customize/migrate-to-v2/migration-prompt.js +1 -0
- package/dist/customize/sections/llm-provider/custom-provider-form.js +24 -7
- package/dist/customize/sections/llm-provider/custom-provider-panel.js +44 -8
- package/dist/customize/sections/llm-provider/types.d.ts +3 -0
- package/dist/customize/sections/llm-provider/utils.js +2 -2
- package/dist/customize/sections/view/agent-editor-access-fields.d.ts +9 -16
- package/dist/customize/sections/view/agent-editor-access-fields.js +30 -30
- package/dist/customize/sections/view/agent-editor-basics-fields.js +3 -10
- package/dist/customize/sections/view/agent-editor-primitives.d.ts +28 -10
- package/dist/customize/sections/view/agent-editor-primitives.js +32 -19
- package/dist/customize/sections/view/agent-editor.js +3 -3
- package/dist/customize/sections/view/channels-view.js +2 -6
- package/dist/customize/sections/view/gateway/gateway-routing.js +3 -3
- package/dist/customize/sections/view/grant-mode-field.d.ts +2 -5
- package/dist/customize/sections/view/grant-mode-field.js +10 -18
- package/dist/customize/sections/view/permission-editor.js +11 -8
- package/dist/customize/sections/view/secrets-view.js +2 -1
- package/dist/customize/settings/tabs/general-tab.d.ts +9 -2
- package/dist/customize/settings/tabs/general-tab.js +33 -11
- package/dist/customize/settings/tabs/transfer-workspace-dialog.d.ts +4 -0
- package/dist/customize/settings/tabs/transfer-workspace-dialog.js +82 -0
- package/dist/customize/shared/new-entity-menu.js +4 -1
- package/dist/customize/skills/skills-page.js +4 -5
- package/dist/customize/triggers/schedule/then-fields.js +1 -5
- package/dist/customize/triggers/schedule-view.js +4 -2
- package/dist/files/project/api/runtime-files.js +3 -2
- package/dist/files/project/file-source.js +18 -1
- package/dist/files/project/hooks/use-file-content.js +2 -0
- package/dist/files/viewer/file-header.js +1 -1
- package/dist/files/viewer/file-preview-modal.d.ts +1 -1
- package/dist/files/viewer/file-preview-modal.js +12 -2
- package/dist/files/viewer/file-source.d.ts +3 -1
- package/dist/files/viewer/use-file-content-state.js +26 -7
- package/dist/files/viewer/viewer-actions.js +2 -2
- package/dist/i18n/en.d.ts +393 -12
- package/dist/i18n/en.js +400 -19
- package/dist/internal/components/agent-mark.d.ts +38 -0
- package/dist/internal/components/agent-mark.js +38 -0
- package/dist/internal/files/types.d.ts +2 -0
- package/dist/internal/goals/goal-meta.d.ts +30 -0
- package/dist/internal/goals/goal-meta.js +55 -0
- package/dist/internal/lib/pixel-mark.d.ts +33 -0
- package/dist/internal/lib/pixel-mark.js +152 -0
- package/dist/internal/lib/query-client-singleton.d.ts +9 -3
- package/dist/internal/lib/query-client-singleton.js +18 -12
- package/dist/internal/lib/use-project-feature-flags.js +3 -3
- package/dist/internal/share/session-shares-modal.js +3 -7
- package/dist/internal/stores/kortix-computer-store.js +14 -2
- package/dist/internal/stores/session-hover-store.d.ts +32 -0
- package/dist/internal/stores/session-hover-store.js +38 -0
- package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
- package/dist/internal/stores/sidebar-nav-store.js +3 -1
- package/dist/lib/routes.d.ts +2 -0
- package/dist/lib/routes.js +1 -0
- package/dist/markdown/genui/components/inline.d.ts +1 -1
- package/dist/markdown/setup-links/app-credentials-form.d.ts +12 -0
- package/dist/markdown/setup-links/app-credentials-form.js +115 -0
- package/dist/markdown/setup-links/secret-intake-form.d.ts +10 -0
- package/dist/markdown/setup-links/secret-intake-form.js +1 -1
- package/dist/primitives/entity-avatar.d.ts +4 -1
- package/dist/primitives/entity-avatar.js +4 -2
- package/dist/primitives/settings-section-header.d.ts +1 -1
- package/dist/primitives/user-avatar.d.ts +3 -1
- package/dist/primitives/user-avatar.js +4 -2
- package/dist/providers/ui-provider.d.ts +1 -1
- package/dist/session/composer/agent-selector.d.ts +7 -1
- package/dist/session/composer/agent-selector.js +20 -10
- package/dist/session/composer/attachment-tile.d.ts +0 -2
- package/dist/session/composer/attachment-tile.js +0 -8
- package/dist/session/composer/clipboard-files.d.ts +2 -1
- package/dist/session/composer/composer-draft-recovery.d.ts +1 -2
- package/dist/session/composer/composer-draft-recovery.js +0 -13
- package/dist/session/composer/context-ring.d.ts +1 -1
- package/dist/session/composer/context-ring.js +2 -2
- package/dist/session/composer/context-usage.d.ts +8 -6
- package/dist/session/composer/context-usage.js +9 -7
- package/dist/session/composer/inbox-row-claims.d.ts +2 -2
- package/dist/session/composer/inbox-row-claims.js +1 -1
- package/dist/session/composer/queue-projection.d.ts +3 -2
- package/dist/session/composer/queued-prompt-edit.d.ts +1 -1
- package/dist/session/composer/reasoning-effort-selector.d.ts +2 -1
- package/dist/session/composer/session-composer-readiness.d.ts +7 -22
- package/dist/session/composer/session-composer-readiness.js +12 -40
- package/dist/session/composer/token-progress.d.ts +1 -1
- package/dist/session/composer/token-progress.js +6 -6
- package/dist/session/header/export-transcript-modal.d.ts +3 -2
- package/dist/session/header/export-transcript-modal.js +6 -3
- package/dist/session/header/session-goal-indicator.d.ts +1 -0
- package/dist/session/header/session-goal-indicator.js +41 -0
- package/dist/session/header/session-site-header.js +2 -1
- package/dist/session/models/model-selector.d.ts +3 -1
- package/dist/session/models/model-selector.js +3 -2
- package/dist/session/panel/easy/plan-card.d.ts +2 -0
- package/dist/session/panel/easy/plan-card.js +2 -0
- package/dist/session/panel/session-action-panel-column.js +11 -1
- package/dist/session/panel/session-audit-shared.d.ts +0 -4
- package/dist/session/panel/session-audit-shared.js +1 -1
- package/dist/session/panel/session-changes-shared.d.ts +2 -1
- package/dist/session/prompts/session-permission-prompt.js +6 -3
- package/dist/session/status/provisioning-failure.d.ts +2 -1
- package/dist/session/status/session-error-banner.js +8 -6
- package/dist/session/status/session-starting-loader.d.ts +24 -31
- package/dist/session/status/session-starting-loader.js +32 -81
- package/dist/session/thread/chat-minimap-items.d.ts +4 -4
- package/dist/session/thread/first-prompt-handover.d.ts +3 -2
- package/dist/session/thread/message-parsing.d.ts +3 -3
- package/dist/session/thread/plan-surface.d.ts +1 -0
- package/dist/session/thread/plan-surface.js +1 -0
- package/dist/session/thread/session-chat.js +5 -12
- package/dist/session/thread/session-context-metrics.d.ts +15 -0
- package/dist/session/thread/session-context-metrics.js +10 -1
- package/dist/session/thread/session-context-modal.d.ts +4 -2
- package/dist/session/thread/session-context-modal.js +16 -9
- package/dist/session/thread/session-history-scroll.d.ts +2 -1
- package/dist/session/thread/session-thread.js +1 -1
- package/dist/session/thread/transcript.d.ts +4 -0
- package/dist/session/thread/transcript.js +18 -5
- package/dist/session/thread/turn-share.d.ts +16 -0
- package/dist/session/thread/turn-share.js +162 -0
- package/dist/session/tool/tools/apply-patch-tool.d.ts +1 -0
- package/dist/session/tool/tools/apply-patch-tool.js +1 -0
- package/dist/session/tool/tools/pty-kill-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-kill-tool.js +1 -0
- package/dist/session/tool/tools/pty-read-tool.d.ts +2 -0
- package/dist/session/tool/tools/pty-read-tool.js +2 -0
- package/dist/session/tool/tools/pty-spawn-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-spawn-tool.js +1 -0
- package/dist/session/tool/tools/pty-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/pty-write-tool.js +1 -0
- package/dist/session/tool/tools/todo-write-tool.d.ts +1 -0
- package/dist/session/tool/tools/todo-write-tool.js +1 -0
- package/dist/session/turn/message-author-label.js +6 -2
- package/dist/session/turn/plan-anchor.d.ts +3 -0
- package/dist/session/turn/plan-anchor.js +3 -0
- package/dist/session/turn/plan-card.d.ts +7 -0
- package/dist/session/turn/plan-card.js +7 -0
- package/dist/styles.css +1 -1
- package/dist/terminal/pty-connection.d.ts +5 -4
- package/dist/terminal/session-terminal-connect-bar.js +2 -1
- package/dist/workspace/index.d.ts +1 -0
- package/dist/workspace/index.js +1 -0
- package/dist/workspace/saved-session-skeleton-shape.d.ts +4 -3
- package/dist/workspace/saved-session-skeleton.d.ts +5 -1
- package/dist/workspace/saved-session-skeleton.js +5 -4
- package/dist/workspace/session-workspace.js +4 -5
- package/dist/workspace/sidebar/footer/project-goals-nav.d.ts +6 -0
- package/dist/workspace/sidebar/footer/project-goals-nav.js +26 -0
- package/dist/workspace/sidebar/modal/session-embed-section.d.ts +25 -0
- package/dist/workspace/sidebar/modal/session-embed-section.js +71 -0
- package/dist/workspace/sidebar/modal/share-session-modal.js +2 -2
- package/dist/workspace/sidebar/nav-row-meta.js +14 -2
- package/dist/workspace/sidebar/project-nav-items.js +3 -0
- package/dist/workspace/sidebar/project-session-list.js +12 -3
- package/dist/workspace/sidebar/session-brief-hover-card.js +33 -9
- package/package.json +12 -4
- package/skills/kortix-ui/references/modules.md +2 -0
- package/dist/session/status/use-readiness-settling.d.ts +0 -18
- package/dist/session/status/use-readiness-settling.js +0 -43
- package/dist/workspace/sidebar/modal/session-public-link-section.d.ts +0 -13
- package/dist/workspace/sidebar/modal/session-public-link-section.js +0 -66
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { AgentMark } from '../../internal/components/agent-mark.js';
|
|
3
4
|
import { EntityFilePane } from '../shared/entity/entity-modal.js';
|
|
4
5
|
import { agentStatus } from './agent-status.js';
|
|
5
6
|
import { useDefaultAgent } from './use-default-agent.js';
|
|
@@ -86,7 +87,8 @@ import { FadedScrollArea } from '../../primitives/faded-scroll-area.js';
|
|
|
86
87
|
import { SETTINGS_SIDEBAR_WIDTH_PX } from '../../internal/account-hub/account-settings-shell.js';
|
|
87
88
|
import { capabilityTabHref } from '../../internal/lib/capability-tab-routes.js';
|
|
88
89
|
import { useIsMobile } from '../../internal/hooks/index.js';
|
|
89
|
-
import { EditorSectionStyleProvider } from '../sections/view/agent-editor-primitives.js';
|
|
90
|
+
import { EditorSection, EditorSectionStyleProvider } from '../sections/view/agent-editor-primitives.js';
|
|
91
|
+
import { SettingsSectionHeader } from '../../primitives/settings-section-header.js';
|
|
90
92
|
import { AgentDrivesSection } from './agent-drives-section.js';
|
|
91
93
|
import { AgentAuthorityCard } from './agent-authority-card.js';
|
|
92
94
|
import { AgentModel, AgentScope } from './agent-detail-aside.js';
|
|
@@ -184,7 +186,7 @@ function AgentPageFrame({ header, section, sections, sectionHref, pane, footer,
|
|
|
184
186
|
// The Settings rail's row dialect (`project-settings-page.tsx`).
|
|
185
187
|
'gap-2 px-2.5 py-1 font-normal transition-none has-[>svg]:px-2.5', 'text-foreground data-[state=inactive]:text-foreground hover:bg-hover hover:text-foreground', 'data-[state=active]:bg-active data-[state=active]:font-medium', '[&_svg]:text-muted-foreground data-[state=active]:[&_svg]:text-foreground'), children: _jsxs(Link, { href: sectionHref(item.key), prefetch: true, children: [_jsx(SectionIcon, { className: "size-4 shrink-0" }), _jsx("span", { className: cn(!horizontal && 'truncate'), children: item.label })] }) }, item.key));
|
|
186
188
|
};
|
|
187
|
-
return (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [_jsx("div", { className: "border-border/60 shrink-0 border-b px-5 py-3", children: header }), _jsxs("div", { className: cn('min-h-0 flex-1', isMobile ? 'flex flex-col overflow-hidden' : 'grid overflow-hidden'), style: isMobile ? undefined : { gridTemplateColumns: `${SETTINGS_SIDEBAR_WIDTH_PX}px 1fr` }, children: [isMobile ? (_jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "border-border/60 flex h-auto shrink-0 items-center border-b", children: _jsx(FadedScrollArea, { orientation: "horizontal", fadeColor: "from-background", className: "min-w-0 flex-1 py-2", children: _jsx(Tabs, { value: section, className: "w-fit", children: _jsx(TabsList, { orientation: "horizontal", className: "w-fit", children: items.map((item) => trigger(item, true)) }) }) }) })) : (_jsx("aside", { className: "flex min-h-0 flex-col border-r bg-inherit", children: _jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "flex min-h-0 flex-1 [scrollbar-width:none] flex-col gap-4 overflow-y-auto px-2 pt-3 pb-2 [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: _jsx(Tabs, { value: section, orientation: "vertical", className: "space-y-3", children: groups.map((g) => (_jsxs("div", { children: [showGroupLabels ? (_jsx("div", { className: "text-muted-foreground flex h-7 items-center px-2.5 text-xs font-medium", children: g.group })) : null, _jsx(TabsList, { orientation: "vertical", className: "w-full", children: g.items.map((item) => trigger(item, false)) })] }, g.group))) }) }) })), _jsx("main", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-2xl px-
|
|
189
|
+
return (_jsxs("div", { className: "relative flex h-full min-h-0 flex-1 flex-col", children: [_jsx("div", { className: "border-border/60 shrink-0 border-b px-5 py-3", children: header }), _jsxs("div", { className: cn('min-h-0 flex-1', isMobile ? 'flex flex-col overflow-hidden' : 'grid overflow-hidden'), style: isMobile ? undefined : { gridTemplateColumns: `${SETTINGS_SIDEBAR_WIDTH_PX}px 1fr` }, children: [isMobile ? (_jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "border-border/60 flex h-auto shrink-0 items-center border-b", children: _jsx(FadedScrollArea, { orientation: "horizontal", fadeColor: "from-background", className: "min-w-0 flex-1 py-2", children: _jsx(Tabs, { value: section, className: "w-fit", children: _jsx(TabsList, { orientation: "horizontal", className: "w-fit", children: items.map((item) => trigger(item, true)) }) }) }) })) : (_jsx("aside", { className: "flex min-h-0 flex-col border-r bg-inherit", children: _jsx("nav", { "aria-label": tI18nComplete.raw('text7be318295927'), className: "flex min-h-0 flex-1 [scrollbar-width:none] flex-col gap-4 overflow-y-auto px-2 pt-3 pb-2 [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", children: _jsx(Tabs, { value: section, orientation: "vertical", className: "space-y-3", children: groups.map((g) => (_jsxs("div", { children: [showGroupLabels ? (_jsx("div", { className: "text-muted-foreground flex h-7 items-center px-2.5 text-xs font-medium", children: g.group })) : null, _jsx(TabsList, { orientation: "vertical", className: "w-full", children: g.items.map((item) => trigger(item, false)) })] }, g.group))) }) }) })), _jsx("main", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-2xl px-4 py-10 pb-32 lg:py-14 lg:pb-32", children: pane }) })] }), footer] }));
|
|
188
190
|
}
|
|
189
191
|
function CenteredState({ children }) {
|
|
190
192
|
return (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx("div", { className: "mx-auto w-full max-w-3xl px-6 py-16", children: children }) }));
|
|
@@ -237,7 +239,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
|
|
|
237
239
|
: null;
|
|
238
240
|
const makeDefault = useDefaultAgent(projectId);
|
|
239
241
|
const pathname = useUiPathname();
|
|
240
|
-
return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(AgentShareControl, { projectId: projectId, agentName: agent.name, peopleHref: `${pathname}?section=people` }), startBlocked ? (_jsx(Hint, { label: startBlocked, children: _jsx("span", { className: "inline-flex", children: _jsxs(Button, { size: "sm", disabled: true, children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] }) }) })) : (_jsxs(Button, { size: "sm", onClick: () => startSession({ create: { agent_name: agent.name } }), children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] })), canWrite ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "
|
|
242
|
+
return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(AgentShareControl, { projectId: projectId, agentName: agent.name, peopleHref: `${pathname}?section=people` }), startBlocked ? (_jsx(Hint, { label: startBlocked, children: _jsx("span", { className: "inline-flex", children: _jsxs(Button, { size: "sm", disabled: true, children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] }) }) })) : (_jsxs(Button, { size: "sm", onClick: () => startSession({ create: { agent_name: agent.name } }), children: [_jsx(PlayIcon, { weight: "fill", className: "size-3.5 shrink-0" }), tI18nComplete.raw('textb1c52ee3677d')] })), canWrite ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon-sm", "aria-label": tI18nComplete.raw('textf8d46c2570e7'), children: _jsx(DotsThreeIcon, { className: "size-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsxs(DropdownMenuItem, { disabled: isDefault || agent.enabled === false || makeDefault.isPending, onSelect: () => makeDefault.mutate(agent.name), children: [_jsx(StarIcon, { className: "size-4" }), isDefault
|
|
241
243
|
? tI18nComplete.raw('texte8cb80e5c5cb')
|
|
242
244
|
: tI18nComplete.raw('texte8f43f910a4e')] }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { disabled: configure.pending, onSelect: () => setConfirmEditSource(true), children: tI18nComplete.raw('text4930821dd07b') })] })] })) : null, _jsx(ConfirmDialog, { open: confirmEditSource, onOpenChange: setConfirmEditSource, title: tI18nComplete.raw('text24ba36a8ec86'), description: tI18nComplete('text13324ae62b23', { value0: agent.name }), confirmLabel: tI18nComplete.raw('text0bce1a892dec'), cancelLabel: tI18nComplete.raw('text19766ed6ccb2'), onConfirm: () => {
|
|
243
245
|
setConfirmEditSource(false);
|
|
@@ -248,7 +250,7 @@ function AgentActions({ projectId, agent, config, canWrite, }) {
|
|
|
248
250
|
function AgentChips({ agent, config, size, }) {
|
|
249
251
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
250
252
|
const { showMode, isDefault, disabled } = agentStatus(agent, config);
|
|
251
|
-
return (_jsxs("span", { className: "flex items-center gap-1.5", children: [showMode ? (_jsx(Badge, { variant: "
|
|
253
|
+
return (_jsxs("span", { className: "flex items-center gap-1.5", children: [showMode ? (_jsx(Badge, { variant: "secondary", size: size, children: formatMode(agent.mode ?? '') })) : null, isDefault ? (_jsx(Badge, { variant: "secondary", size: size, children: tI18nComplete.raw('text21b111cbfe6e') })) : null, disabled ? (_jsx(Badge, { variant: "muted", size: size, children: tI18nComplete.raw('text75081b593d15') })) : null] }));
|
|
252
254
|
}
|
|
253
255
|
/**
|
|
254
256
|
* Breadcrumb, avatar, name, chips, actions — the full header at the top of
|
|
@@ -257,7 +259,7 @@ function AgentChips({ agent, config, size, }) {
|
|
|
257
259
|
*/
|
|
258
260
|
function AgentHeader({ projectId, agent, config, canWrite, children, }) {
|
|
259
261
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
260
|
-
return (_jsxs("header", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("nav", { "aria-label": tI18nComplete.raw('text2bd873d6c734'), className: "flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Link, { href: capabilityTabHref(projectId, 'agent'), prefetch: true, className: "text-muted-foreground hover:text-foreground shrink-0 transition-colors", children: tI18nComplete.raw('text279b44d2ab4b') }), _jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground
|
|
262
|
+
return (_jsxs("header", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("nav", { "aria-label": tI18nComplete.raw('text2bd873d6c734'), className: "flex min-w-0 items-center gap-1.5 text-sm", children: [_jsx(Link, { href: capabilityTabHref(projectId, 'agent'), prefetch: true, className: "text-muted-foreground hover:text-foreground shrink-0 transition-colors", children: tI18nComplete.raw('text279b44d2ab4b') }), _jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground size-3.5 shrink-0" }), _jsx(AgentMark, { slug: agent.name }), _jsx("h1", { className: "text-foreground truncate text-sm font-semibold", children: isMetaAgentName(agent.name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(agent.name) }), _jsx(AgentChips, { agent: agent, config: config, size: "xs" })] }), _jsx(AgentActions, { projectId: projectId, agent: agent, config: config, canWrite: canWrite })] }), children] }));
|
|
261
263
|
}
|
|
262
264
|
// ─── Editable body ─────────────────────────────────────────────────────────
|
|
263
265
|
/** What a v1 project, or a reader without write, can still see. */
|
|
@@ -318,34 +320,40 @@ function EditableAgentPage({ projectId, agent, config, initial, schemaVersion, }
|
|
|
318
320
|
}
|
|
319
321
|
/**
|
|
320
322
|
* The Overview topic — what the agent IS: one line of description and the
|
|
321
|
-
* instructions
|
|
323
|
+
* instructions. Laid out like a Settings pane: the pane title, then each
|
|
324
|
+
* field under its own section heading. A field is its own surface; no box
|
|
325
|
+
* wraps a heading and a field together.
|
|
322
326
|
*/
|
|
323
327
|
function OverviewPane({ description, onDescriptionChange, descriptionHelp, prompt, onPromptChange, }) {
|
|
324
328
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
325
|
-
|
|
329
|
+
const tEditor = useI18nTranslations('agentEditor');
|
|
330
|
+
return (_jsxs(_Fragment, { children: [_jsx(SettingsSectionHeader, { title: tEditor('overviewTitle'), className: "pb-1" }), _jsx(EditorSection, { title: tI18nComplete.raw('text526e0087cc3f'), description: descriptionHelp, bare: true, children: _jsx(Textarea, { "aria-label": tI18nComplete.raw('text526e0087cc3f'), value: description, placeholder: tI18nComplete.raw('text446f4eabf99f'), minHeight: 44, className: "text-sm", onChange: (e) => onDescriptionChange(e.target.value) }) }), _jsx(InstructionsSection, { value: prompt, onChange: onPromptChange })] }));
|
|
326
331
|
}
|
|
327
332
|
/**
|
|
328
333
|
* The instructions — the agent's system prompt, and the thing that most IS
|
|
329
334
|
* the agent. Two views of one field: Edit is the textarea, Preview renders
|
|
330
335
|
* the markdown the way the agent's source file reads. It opens on Preview
|
|
331
336
|
* when there is something to read and on Edit when there is not; the toggle
|
|
332
|
-
* is
|
|
333
|
-
* pages use for their filters.
|
|
337
|
+
* is the segmented `TabsList` at the section heading's right edge.
|
|
334
338
|
*/
|
|
335
|
-
function
|
|
339
|
+
function InstructionsSection({ value, onChange, }) {
|
|
336
340
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
337
341
|
const [view, setView] = useState(value.trim() ? 'preview' : 'edit');
|
|
338
|
-
return (
|
|
342
|
+
return (_jsx(EditorSection, { title: tI18nComplete.raw('text934652dce41d'), description: tI18nComplete.raw('text5fe8921b712c'), trailing: _jsx(Tabs, { value: view, onValueChange: (next) => setView(next), children: _jsxs(TabsList, { "aria-label": tI18nComplete.raw('texte75bb2d0d554'), children: [_jsx(TabsTrigger, { value: "edit", children: tI18nComplete.raw('text464c4ffd019e') }), _jsx(TabsTrigger, { value: "preview", children: tI18nComplete.raw('text324b134f57c7') })] }) }), bare: true, children: view === 'edit' ? (_jsx(Textarea, { "aria-label": tI18nComplete.raw('text934652dce41d'), value: value, placeholder: tI18nComplete.raw('text1436469ad07f'), minHeight: 420, autoFocus: value.trim().length > 0, className: "font-mono text-xs leading-relaxed", onChange: (e) => onChange(e.target.value) })) : (_jsx("div", { className: "bg-popover min-h-[420px] rounded-md border px-5 py-4", children: _jsx(MarkdownWithFrontmatter, { content: value }) })) }));
|
|
339
343
|
}
|
|
340
344
|
/**
|
|
341
345
|
* The save bar. Exists only while there is something to save — an always-on
|
|
342
|
-
* disabled pair of buttons is chrome that never earns its row. It
|
|
343
|
-
* the
|
|
344
|
-
*
|
|
346
|
+
* disabled pair of buttons is chrome that never earns its row. It floats at
|
|
347
|
+
* the bottom centre of the page in the Drive selection bar's shape
|
|
348
|
+
* (`project-files/components/drive-selection-bar.tsx`): a `bg-popover` card
|
|
349
|
+
* as wide as its content — the state as a title over one muted line, then
|
|
350
|
+
* Discard and Save as a spaced pair (a confirm is never glued to its cancel).
|
|
351
|
+
* It sits outside both scrollers, so it is reachable
|
|
352
|
+
* from any point in a long instruction set.
|
|
345
353
|
*/
|
|
346
354
|
function SaveBar({ dirty, pending, onDiscard, onSave, }) {
|
|
347
355
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
348
|
-
return (_jsx(AnimatePresence, { initial: false, children: dirty ? (_jsx(m.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 8 }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "
|
|
356
|
+
return (_jsx(AnimatePresence, { initial: false, children: dirty ? (_jsx(m.div, { initial: { opacity: 0, y: 8 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 8 }, transition: { type: 'spring', duration: 0.3, bounce: 0 }, className: "pointer-events-none absolute inset-x-0 bottom-4 z-30 flex justify-center px-4", children: _jsxs("div", { className: "bg-popover text-popover-foreground border-border pointer-events-auto flex max-w-full items-center gap-3 rounded-lg border p-2 pl-3.5 shadow-md", children: [_jsxs("div", { className: "min-w-0", "aria-live": "polite", children: [_jsx("p", { className: "text-foreground truncate text-sm font-medium", children: tI18nComplete.raw('textefa5855ff509') }), _jsx("p", { className: "text-muted-foreground truncate text-xs", children: tI18nComplete.raw('textc8134fda3694') })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { variant: "secondary", size: "sm", onClick: onDiscard, disabled: pending, children: tI18nComplete.raw('texteb1a70e39274') }), _jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: onSave, disabled: pending, children: [pending ? _jsx(Loading, { className: "size-3.5 shrink-0" }) : null, tI18nComplete.raw('text1509f561f241'), _jsx(Kbd, { children: tI18nComplete.raw('text8cd003e6301b') })] })] })] }) }, "save-bar")) : null }));
|
|
349
357
|
}
|
|
350
358
|
/**
|
|
351
359
|
* Mod+S saves. The page is a document you edit, and every editor a person
|
|
@@ -445,10 +453,10 @@ function ReadOnlyAgentPage({ projectId, agent, config, canWrite, showUpgradeHint
|
|
|
445
453
|
});
|
|
446
454
|
const pathname = useUiPathname();
|
|
447
455
|
const section = useAgentSection(isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS);
|
|
448
|
-
const source = (
|
|
456
|
+
const source = (_jsx(EditorSection, { title: tI18nComplete.raw('text934652dce41d'), description: _jsx("span", { className: "font-mono", children: sourcePath }), lead: true, bare: true, children: _jsx("div", { className: "bg-popover overflow-hidden rounded-md border", children: _jsx(EntityFilePane, { path: sourcePath, content: fileQuery.data?.content, isLoading: fileQuery.isLoading, isError: fileQuery.isError, error: fileQuery.error, onRetry: () => {
|
|
449
457
|
void fileQuery.refetch();
|
|
450
|
-
}, skeletonRows: 4, errorTitle: tI18nComplete.raw('text2f2f540650bf') })
|
|
451
|
-
return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: canWrite && !isPlatform, children: agent.description ? (_jsx("p", { className: "text-muted-foreground max-w-2xl text-sm text-pretty", children: agent.description })) : null }), section: section, sections: isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS, sectionHref: sectionHrefFor(pathname), pane: _jsx(EditorSectionStyleProvider, { value: "panel", children: _jsxs("div", { className: "space-y-
|
|
458
|
+
}, skeletonRows: 4, errorTitle: tI18nComplete.raw('text2f2f540650bf') }) }) }));
|
|
459
|
+
return (_jsx(AgentPageFrame, { header: _jsx(AgentHeader, { projectId: projectId, agent: agent, config: config, canWrite: canWrite && !isPlatform, children: agent.description ? (_jsx("p", { className: "text-muted-foreground max-w-2xl text-sm text-pretty", children: agent.description })) : null }), section: section, sections: isPlatform ? PLATFORM_SECTIONS : READ_ONLY_SECTIONS, sectionHref: sectionHrefFor(pathname), pane: _jsx(EditorSectionStyleProvider, { value: "panel", children: _jsxs("div", { className: "space-y-8", children: [isPlatform ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('text85b7a59cdbd5'), children: tI18nComplete.raw('textd9c0c74b514b') })) : showUpgradeHint ? (_jsx(InfoBanner, { tone: "info", title: tI18nComplete.raw('textdc7ab144ca89'), children: tUpgrades.rich('agentEditorHint', {
|
|
452
460
|
code: (chunks) => _jsx("span", { className: "font-mono", children: chunks }),
|
|
453
|
-
}) })) : null, isPlatform ? null : section === 'overview' ? (_jsxs(_Fragment, { children: [source, _jsx(AgentScope, { projectId: projectId, agentName: agent.name, scope: agent.scope })] })) : section === 'people' ? (_jsx(AgentPeopleSection, { projectId: projectId, agentName: agent.name })) : section === 'triggers' ? (_jsx(AgentTriggersSection, { projectId: projectId, agentName: agent.name, defaultAgent: config.default_agent ?? config.open_code_default_agent })) : section === 'actions' ? (_jsx(AgentAuthorityCard, { projectId: projectId, agentName: agent.name, grant: agent.scope?.kortix_permissions ?? agent.scope?.kortix_cli })) : (_jsxs("div", { className: "space-y-
|
|
461
|
+
}) })) : null, isPlatform ? null : section === 'overview' ? (_jsxs(_Fragment, { children: [source, _jsx(AgentScope, { projectId: projectId, agentName: agent.name, scope: agent.scope })] })) : section === 'people' ? (_jsx(AgentPeopleSection, { projectId: projectId, agentName: agent.name })) : section === 'triggers' ? (_jsx(AgentTriggersSection, { projectId: projectId, agentName: agent.name, defaultAgent: config.default_agent ?? config.open_code_default_agent })) : section === 'actions' ? (_jsx(AgentAuthorityCard, { projectId: projectId, agentName: agent.name, grant: agent.scope?.kortix_permissions ?? agent.scope?.kortix_cli })) : (_jsxs("div", { className: "space-y-3", children: [_jsx(AgentModel, { projectId: projectId, agentName: agent.name }), _jsx("p", { className: "text-muted-foreground text-xs text-pretty", children: tI18nComplete.raw('text9dee2a1acfd9') })] }))] }) }) }));
|
|
454
462
|
}
|
|
@@ -37,7 +37,8 @@ import { EntityAvatar } from '../../primitives/entity-avatar.js';
|
|
|
37
37
|
import { Skeleton } from '../../primitives/skeleton.js';
|
|
38
38
|
import { errorToast, successToast } from '../../primitives/toast.js';
|
|
39
39
|
import { UserAvatar } from '../../primitives/user-avatar.js';
|
|
40
|
-
import {
|
|
40
|
+
import { EmptyState } from '../../primitives/empty-state.js';
|
|
41
|
+
import { EditorList, EditorSection } from '../sections/view/agent-editor-primitives.js';
|
|
41
42
|
import { AccessDialog, } from '../../access/access/access-dialog.js';
|
|
42
43
|
import { builtinRole, customRole, ROLE_NONE } from '../../access/access/role-select.js';
|
|
43
44
|
import { PROJECT_ACTIONS } from '../../internal/lib/project-actions.js';
|
|
@@ -133,20 +134,20 @@ export function AgentPeopleSection({ projectId, agentName, }) {
|
|
|
133
134
|
});
|
|
134
135
|
setEditOpen(true);
|
|
135
136
|
};
|
|
136
|
-
return (_jsxs(EditorSection, { title: tI18nComplete.raw('text82d9af69e1a1'), description: tI18nComplete.raw('text96feb5be077d'),
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
return (_jsxs(EditorSection, { title: tI18nComplete.raw('text82d9af69e1a1'), description: tI18nComplete.raw('text96feb5be077d'), trailing: accountId ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setGrantOpen(true), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text8693768c7e08')] })) : undefined, lead: true, bare: true, children: [_jsx("div", { children: grantsQuery.isLoading ? (_jsx(EditorList, { children: [0, 1].map((i) => (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [_jsx(Skeleton, { className: "size-8 rounded-md" }), _jsx(Skeleton, { className: "h-4 w-40 rounded-sm" })] }, i))) })) : assigned.length === 0 ? (_jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('text011c4e48df01') })) : (_jsx(EditorList, { children: assigned.map((g) => {
|
|
138
|
+
const member = g.principal_type === 'member'
|
|
139
|
+
? accessQuery.data?.members.find((m) => m.user_id === g.principal_id)
|
|
140
|
+
: undefined;
|
|
141
|
+
return (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [g.principal_type === 'group' ? (_jsx(EntityAvatar, { icon: UsersIcon, size: "md" })) : g.principal_type === 'project' ? (_jsx(EntityAvatar, { icon: UsersThreeIcon, size: "md" })) : (_jsx(UserAvatar, { email: member?.email ?? g.principal_label, size: "md" })), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: g.principal_type === 'project'
|
|
142
|
+
? tSharing('everyone', { project: g.principal_label })
|
|
143
|
+
: g.principal_label }), _jsxs("span", { className: "text-muted-foreground block truncate text-xs", children: [g.principal_type === 'project'
|
|
144
|
+
? tSharing('everyoneMeta')
|
|
145
|
+
: g.principal_type === 'group'
|
|
146
|
+
? tSharing('groupMeta')
|
|
147
|
+
: tSharing('memberMeta'), g.expires_at
|
|
148
|
+
? tI18nComplete('textdc40ace03968', {
|
|
149
|
+
value0: new Date(g.expires_at).toLocaleDateString(),
|
|
150
|
+
})
|
|
151
|
+
: '', g.orphaned ? tI18nComplete.raw('textc795f93da2e5') : ''] })] }), g.principal_type === 'project' ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setStopSharing(g), children: tSharing('removeEveryone') })) : (_jsxs(Button, { variant: "ghost", size: "sm", className: "gap-1.5", onClick: () => openEdit(g), disabled: !accountId, children: [_jsx(PencilSimpleIcon, { className: "size-3.5 shrink-0" }), tI18nComplete.raw('text464c4ffd019e')] }))] }, g.grant_id));
|
|
152
|
+
}) })) }), accountId ? (_jsx(AccessDialog, { open: grantOpen, onOpenChange: setGrantOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'grant' }, initialAgentIds: [agentName] })) : null, accountId && editTarget ? (_jsx(AccessDialog, { open: editOpen, onOpenChange: setEditOpen, accountId: accountId, scope: { kind: 'project', projectId, projectName }, mode: { kind: 'edit', principal: editTarget.principal, current: editTarget.current }, inheritedFrom: editTarget.inheritedFrom }, `${editTarget.principal.type}:${editTarget.principal.id}`)) : null, _jsx(ConfirmDialog, { open: stopSharing !== null, onOpenChange: (open) => !open && setStopSharing(null), title: tSharing('removeEveryoneTitle', { agent: agentName }), description: tSharing('removeEveryoneDescription', { agent: agentName }), confirmLabel: tSharing('removeEveryone'), confirmVariant: "destructive", isPending: stopSharingMutation.isPending, onConfirm: () => stopSharing && stopSharingMutation.mutate(stopSharing.grant_id) })] }));
|
|
152
153
|
}
|
|
@@ -28,7 +28,8 @@ import { Badge } from '../../primitives/badge.js';
|
|
|
28
28
|
import { Button } from '../../primitives/button.js';
|
|
29
29
|
import { ConfirmDialog } from '../../primitives/confirm-dialog.js';
|
|
30
30
|
import { Skeleton } from '../../primitives/skeleton.js';
|
|
31
|
-
import {
|
|
31
|
+
import { EmptyState } from '../../primitives/empty-state.js';
|
|
32
|
+
import { EditorList, EditorSection } from '../sections/view/agent-editor-primitives.js';
|
|
32
33
|
import { useProjectFeatureFlags } from '../../internal/lib/use-project-feature-flags.js';
|
|
33
34
|
import { cn } from '../../internal/lib/utils.js';
|
|
34
35
|
import { listProjectTriggers } from '@kortix/sdk';
|
|
@@ -89,10 +90,10 @@ export function AgentTriggersSection({ projectId, agentName, defaultAgent, }) {
|
|
|
89
90
|
setSelectedSlug(null);
|
|
90
91
|
},
|
|
91
92
|
});
|
|
92
|
-
return (_jsxs(EditorSection, { title: tI18nComplete.raw('texte62f2148a64d'), description: tI18nComplete.raw('text1a1e4d549e85'),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
return (_jsxs(EditorSection, { title: tI18nComplete.raw('texte62f2148a64d'), description: tI18nComplete.raw('text1a1e4d549e85'), trailing: canCreate ? (_jsxs(Button, { variant: "secondary", size: "sm", className: "gap-1.5", onClick: () => setCreateOpen(true), children: [_jsx(PlusIcon, { className: "size-4" }), tI18nComplete.raw('text58fd308928dd')] })) : undefined, lead: true, bare: true, children: [_jsx("div", { children: triggersQuery.isLoading ? (_jsx(EditorList, { children: [0, 1].map((i) => (_jsxs("li", { className: "flex items-center gap-3 px-4 py-2.5", children: [_jsx(Skeleton, { className: "size-8 rounded-sm" }), _jsx(Skeleton, { className: "h-4 w-40 rounded-sm" })] }, i))) })) : mine.length === 0 ? (_jsx(EmptyState, { size: "sm", title: tI18nComplete.raw('textb254c6e02e1a') })) : (_jsx(EditorList, { children: mine.map((trigger) => {
|
|
94
|
+
const KindIcon = KIND_ICON[trigger.type] ?? TimerIcon;
|
|
95
|
+
return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => setSelectedSlug(trigger.slug), className: cn('hover:bg-hover flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors duration-fast', 'focus-visible:bg-hover focus-visible:outline-none'), children: [_jsx("span", { className: cn('flex size-8 shrink-0 items-center justify-center rounded-sm', trigger.enabled ? 'bg-kortix-green/15' : 'bg-muted'), children: _jsx(KindIcon, { weight: "fill", className: cn('size-4', trigger.enabled ? 'text-kortix-green' : 'text-muted-foreground') }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: triggerName(trigger) }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: describeWhen(trigger) })] }), !trigger.enabled ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('texte159b06187d3') })) : null] }) }, trigger.slug));
|
|
96
|
+
}) })) }), canCreate ? (_jsx(TriggerComposer, { projectId: projectId, open: createOpen, onOpenChange: setCreateOpen, initialAgent: agentName, onCreated: (slug) => {
|
|
96
97
|
setCreateOpen(false);
|
|
97
98
|
void invalidate();
|
|
98
99
|
// The composer toasts the creation; straight into the new entry, as the Triggers tab does.
|
|
@@ -16,22 +16,22 @@ import { detectManifestVersion } from '../../internal/lib/manifest-version.js';
|
|
|
16
16
|
import { toArray } from '../shared/utils.js';
|
|
17
17
|
import { newConfigPrompt, useConfigureThread, } from '../use-configure-thread.js';
|
|
18
18
|
import { PROJECT_ACTIONS } from '../../internal/lib/project-actions.js';
|
|
19
|
-
import { Skeleton } from '../../primitives/skeleton.js';
|
|
20
19
|
import { useProjectCan } from '../../internal/lib/use-project-can.js';
|
|
21
20
|
import { cn } from '../../internal/lib/utils.js';
|
|
22
21
|
import { getProjectDetail, listProjectResourceGrants, listProjectTriggers, updateProjectDefaultAgent, } from '@kortix/sdk';
|
|
23
22
|
import { contract, qk, useProjectAccountId } from '@kortix/sdk/react';
|
|
24
23
|
import { capitalizeWords, isMetaAgentName, META_AGENT_DISPLAY_NAME } from '../../_vendor/kortix-shared.js';
|
|
25
|
-
import {
|
|
24
|
+
import { MagnifyingGlassIcon, RobotIcon } from '@phosphor-icons/react';
|
|
26
25
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
27
26
|
import { CapabilityPageShell } from '../shared/capability-page-shell.js';
|
|
28
27
|
import { agentHref } from '../../internal/lib/capability-tab-routes.js';
|
|
29
28
|
import { prefetchAgentConfig } from '../../internal/hooks/projects/use-agent-config.js';
|
|
30
|
-
import { CatalogCard } from '../shared/catalog/catalog-card.js';
|
|
31
29
|
import { catalogEmptyKind } from '../shared/catalog/catalog-empty.js';
|
|
32
30
|
import { CatalogNoMatch } from '../shared/catalog/catalog-empty-state.js';
|
|
33
31
|
import { CatalogGrid } from '../shared/catalog/catalog-grid.js';
|
|
34
|
-
import {
|
|
32
|
+
import { GRID_CLASSNAME } from '../shared/catalog/catalog-grid-tokens.js';
|
|
33
|
+
import { AgentMark } from '../../internal/components/agent-mark.js';
|
|
34
|
+
import { AgentCoverCard } from './agent-cover-card.js';
|
|
35
35
|
import { agentInMode, filterAgents } from './agent-filter.js';
|
|
36
36
|
import { useSlashFocus } from '../../internal/hooks/index.js';
|
|
37
37
|
/**
|
|
@@ -92,8 +92,8 @@ export function AgentsPage({ projectId }) {
|
|
|
92
92
|
}
|
|
93
93
|
return counts;
|
|
94
94
|
}, [triggersQuery.data, config]);
|
|
95
|
-
// Who is granted each agent
|
|
96
|
-
//
|
|
95
|
+
// Who is granted each agent. Same key the agent page's People section
|
|
96
|
+
// reads, manager-gated like it.
|
|
97
97
|
const grantsQuery = useQuery({
|
|
98
98
|
queryKey: qk.project.resourceGrants(projectId),
|
|
99
99
|
queryFn: () => listProjectResourceGrants(projectId),
|
|
@@ -110,6 +110,7 @@ export function AgentsPage({ projectId }) {
|
|
|
110
110
|
}
|
|
111
111
|
return counts;
|
|
112
112
|
}, [grantsQuery.data]);
|
|
113
|
+
const facts = (agent) => (_jsx(AgentCardFacts, { agent: agent, triggerCount: triggerCounts.get(agent.name) ?? 0, grantCount: grantCounts.get(agent.name) ?? 0 }));
|
|
113
114
|
const agents = useMemo(() => toArray(config?.agents), [config]);
|
|
114
115
|
const filtered = useMemo(() => filterAgents(agents, { mode: null, query }), [agents, query]);
|
|
115
116
|
const defaultAgent = config?.default_agent ?? config?.open_code_default_agent ?? null;
|
|
@@ -122,8 +123,6 @@ export function AgentsPage({ projectId }) {
|
|
|
122
123
|
.filter((agent) => agentInMode(agent, 'primary'))
|
|
123
124
|
.sort((a, b) => Number(b.name === defaultAgent) - Number(a.name === defaultAgent)), [filtered, defaultAgent]);
|
|
124
125
|
const subagents = useMemo(() => filtered.filter((agent) => !agentInMode(agent, 'primary')), [filtered]);
|
|
125
|
-
const subagentTotal = agents.filter((agent) => !agentInMode(agent, 'primary')).length;
|
|
126
|
-
const primaryTotal = agents.length - subagentTotal;
|
|
127
126
|
// `null` = render the grid. Otherwise which "nothing to show" copy applies:
|
|
128
127
|
// genuinely zero agents vs. agents exist but this filter/search hid all of
|
|
129
128
|
// them. Telling the user "No agents yet" in the second case is false and
|
|
@@ -133,69 +132,64 @@ export function AgentsPage({ projectId }) {
|
|
|
133
132
|
// title beside it and can be terse; the empty state is the whole screen and
|
|
134
133
|
// has to name what it creates. Both start the same configure thread, so they
|
|
135
134
|
// cannot drift apart.
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
const createButton = (label) => canWrite ? (_jsx(NewEntityMenu, { label: label, pending: configure.pending, onChat: () => configure.start(newConfigPrompt('agent')), manual: {
|
|
135
|
+
// The header button is `h-9`, the height of the `size="sm"` search input
|
|
136
|
+
// beside it. The empty state keeps `sm` to match its `sm` docs link.
|
|
137
|
+
const createButton = (label, size) => canWrite ? (_jsx(NewEntityMenu, { label: label, size: size, pending: configure.pending, onChat: () => configure.start(newConfigPrompt('agent')), manual: {
|
|
140
138
|
description: tI18nComplete.raw('text5210c5acc2cd'),
|
|
141
139
|
href: uiRoutes().files(projectId) ?? customizePath(projectId, 'repo'),
|
|
142
140
|
} })) : null;
|
|
143
|
-
return (_jsxs(CapabilityPageShell, { title: tI18nComplete.raw('text279b44d2ab4b'), description: tI18nComplete.raw('text636453e23d69'), action:
|
|
144
|
-
// The
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
? ` · ${subagentTotal} ${subagentTotal === 1 ? 'subagent' : 'subagents'}`
|
|
148
|
-
: null, ' ', tI18nComplete.raw('text582967534d0f'), ' ', _jsx("span", { className: "font-mono", children: tI18nComplete.raw('text1965f383021e') })] }), _jsx(DefaultAgentSelector, { projectId: projectId, config: config, canWrite: canWrite })] })) : undefined, children: [_jsx(CatalogGrid, { isLoading: detailQuery.isLoading, isError: detailQuery.isError, error: detailQuery.error, onRetry: () => detailQuery.refetch(), isEmpty: emptyKind !== null, empty: emptyKind === 'no-match' ? (_jsx(CatalogNoMatch, { query: query })) : (_jsx(EmptyState, { icon: RobotIcon, size: "sm", title: tI18nComplete.raw('text971b61f8cb4d'), description: tI18nComplete.raw('textc4e4294c6777'), action: createButton('Create an agent'), secondaryAction: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "gap-1.5", children: _jsx("a", { href: "https://opencode.ai/docs/agents/", target: "_blank", rel: "noopener noreferrer", children: tI18nComplete.raw('text7af023c43013') }) }) })), children: primaryAgents.map((agent) => (_jsx(CatalogCard, { href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), subtitle: _jsx("code", { className: "text-muted-foreground text-xs break-all", children: agent.name }), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent, isDefault: defaultAgent === agent.name }), meta: _jsx(AgentCardFacts, { agent: agent, triggerCount: triggerCounts.get(agent.name) ?? 0, peopleCount: canManageMembers ? (grantCounts.get(agent.name) ?? 0) : null }), trailing: _jsx(CardCaret, {}) }, agent.path))) }), !detailQuery.isLoading && !detailQuery.isError && subagents.length > 0 ? (_jsxs("section", { "aria-labelledby": "subagents-heading", className: cn('space-y-2', primaryAgents.length > 0 && 'mt-8'), children: [_jsxs("div", { children: [_jsxs("h2", { id: "subagents-heading", className: "text-foreground text-sm font-medium", children: [tI18nComplete.raw('agentsListSubagentsTitle'), ' ', _jsx("span", { className: "text-muted-foreground font-normal tabular-nums", children: subagents.length })] }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: tI18nComplete.raw('agentsListSubagentsHint') })] }), _jsx("div", { className: cn(DENSE_GRID_CONTAINER_CLASSNAME, '-mx-3'), children: _jsx("div", { className: DENSE_GRID_CLASSNAME, children: subagents.map((agent) => (_jsx(CatalogCard, { variant: "plain", href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), subtitle: _jsx("code", { className: "text-muted-foreground text-xs break-all", children: agent.name }), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent, isDefault: false }), meta: _jsx("span", { className: cn('truncate', agent.model && 'font-mono'), children: agent.model?.split('/').pop() ?? tI18nComplete.raw('text3840d9d29421') }), trailing: _jsx(CardCaret, {}) }, agent.path))) }) })] })) : null] }));
|
|
141
|
+
return (_jsxs(CapabilityPageShell, { title: tI18nComplete.raw('text279b44d2ab4b'), description: tI18nComplete.raw('text636453e23d69'), action:
|
|
142
|
+
// The default-agent selector sits in the header beside New: it is a
|
|
143
|
+
// page-level setting, not a filter on the list.
|
|
144
|
+
_jsxs(_Fragment, { children: [config ? (_jsx(DefaultAgentSelector, { projectId: projectId, config: config, canWrite: canWrite })) : null, createButton('New')] }), search: _jsxs(InputGroupSearch, { children: [_jsx(InputGroupSearchIcon, { children: _jsx(MagnifyingGlassIcon, {}) }), _jsx(InputGroupSearchInput, { ref: searchRef, placeholder: tI18nComplete.raw('text212fd04a1d20'), value: query, onChange: (event) => setQuery(event.target.value), variant: "popover", size: "sm" }), _jsx(InputGroupSearchClear, { onClick: () => setQuery('') })] }), children: [_jsx(CatalogGrid, { isLoading: detailQuery.isLoading, isError: detailQuery.isError, error: detailQuery.error, onRetry: () => detailQuery.refetch(), isEmpty: emptyKind !== null, empty: emptyKind === 'no-match' ? (_jsx(CatalogNoMatch, { query: query })) : (_jsx(EmptyState, { icon: RobotIcon, size: "sm", title: tI18nComplete.raw('text971b61f8cb4d'), description: tI18nComplete.raw('textc4e4294c6777'), action: createButton('Create an agent', 'sm'), secondaryAction: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "gap-1.5", children: _jsx("a", { href: "/docs/project/agents", target: "_blank", rel: "noopener noreferrer", children: tI18nComplete.raw('text7af023c43013') }) }) })), children: primaryAgents.map((agent) => (_jsx(AgentCoverCard, { slug: agent.name, href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent }), coverLabel: defaultAgent === agent.name ? tI18nComplete.raw('text21b111cbfe6e') : undefined, meta: facts(agent) }, agent.path))) }), !detailQuery.isLoading && !detailQuery.isError && subagents.length > 0 ? (_jsxs("section", { "aria-labelledby": "subagents-heading", className: cn('space-y-2', primaryAgents.length > 0 && 'mt-8'), children: [_jsxs("div", { children: [_jsxs("h2", { id: "subagents-heading", className: "text-foreground text-sm font-medium", children: [tI18nComplete.raw('agentsListSubagentsTitle'), ' ', _jsx("span", { className: "text-muted-foreground font-normal tabular-nums", children: subagents.length })] }), _jsx("p", { className: "text-muted-foreground mt-1 text-xs", children: tI18nComplete.raw('agentsListSubagentsHint') })] }), _jsx("div", { className: GRID_CLASSNAME, children: subagents.map((agent) => (_jsx(AgentCoverCard, { slug: agent.name, href: agentHref(projectId, agent.name), onIntent: () => prefetchAgentConfig(queryClient, projectId, agent.name), title: agentTitle(agent.name), description: agent.description, badges: _jsx(AgentCardBadges, { agent: agent }), meta: facts(agent) }, agent.path))) })] })) : null] }));
|
|
149
145
|
}
|
|
150
146
|
function agentTitle(name) {
|
|
151
147
|
return isMetaAgentName(name) ? META_AGENT_DISPLAY_NAME : capitalizeWords(name);
|
|
152
148
|
}
|
|
153
|
-
function CardCaret() {
|
|
154
|
-
return (_jsx(CaretRightIcon, { "aria-hidden": true, className: "text-muted-foreground group-hover:text-foreground mt-0.5 size-4 transition-colors" }));
|
|
155
|
-
}
|
|
156
149
|
/**
|
|
157
|
-
* The
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* printing nothing; triggers and people print their count, including zero,
|
|
161
|
-
* so every card has the same three slots and the eye can compare down a
|
|
162
|
-
* column. People is omitted for a reader who cannot see grants.
|
|
150
|
+
* The access fact (KRTX-3025): "Full access", or the connectors an agent can
|
|
151
|
+
* reach. Nothing for an agent without connectors — the card does not list what
|
|
152
|
+
* an agent lacks.
|
|
163
153
|
*/
|
|
164
|
-
function AgentCardFacts({ agent, triggerCount, peopleCount, }) {
|
|
165
|
-
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
166
|
-
const model = agent.model ? agent.model.split('/').pop() : null;
|
|
167
|
-
const accessLabel = accessLabelOf(agentAccess(agent.scope), tI18nComplete);
|
|
168
|
-
const sep = (_jsx("span", { "aria-hidden": true, className: "text-muted-foreground", children: "\u00B7" }));
|
|
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
|
|
170
|
-
? tI18nComplete.raw('textb6c70926c1aa')
|
|
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
154
|
function accessLabelOf(access, t) {
|
|
175
155
|
if (!access)
|
|
176
156
|
return null;
|
|
177
157
|
if (access.kind === 'full')
|
|
178
158
|
return t.raw('textf19611c61ca5');
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
159
|
+
const connectors = access.parts.find((part) => part.kind === 'connectors')?.value ?? 0;
|
|
160
|
+
if (connectors === 'all')
|
|
161
|
+
return t.raw('textd83250185d41');
|
|
162
|
+
return connectors > 0 ? t('text15589a542412', { count: connectors }) : null;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The card's facts line, joined by `•`: access, the agent's own model,
|
|
166
|
+
* triggers, grants. A fact with nothing to say is left out — no "0 triggers",
|
|
167
|
+
* no "Default model" — so an empty slot never reads as information.
|
|
168
|
+
*/
|
|
169
|
+
function AgentCardFacts({ agent, triggerCount, grantCount, }) {
|
|
170
|
+
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
171
|
+
const facts = [
|
|
172
|
+
accessLabelOf(agentAccess(agent.scope), tI18nComplete),
|
|
173
|
+
agent.model ? agent.model.split('/').pop() : null,
|
|
174
|
+
triggerCount > 0 ? `${triggerCount} ${triggerCount === 1 ? 'trigger' : 'triggers'}` : null,
|
|
175
|
+
grantCount > 0 ? `${grantCount} ${grantCount === 1 ? 'grant' : 'grants'}` : null,
|
|
176
|
+
].filter((fact) => Boolean(fact));
|
|
177
|
+
if (facts.length === 0)
|
|
178
|
+
return null;
|
|
179
|
+
return (_jsx("span", { className: "truncate tabular-nums", children: facts.map((fact, index) => (_jsxs("span", { children: [index > 0 ? (_jsx("span", { "aria-hidden": true, className: "px-1.5", children: "\u2022" })) : null, fact] }, fact))) }));
|
|
187
180
|
}
|
|
188
181
|
/**
|
|
189
|
-
* Card badges — "also a subagent",
|
|
182
|
+
* Card badges — "also a subagent", disabled. The default agent is marked on
|
|
183
|
+
* the cover instead.
|
|
190
184
|
*
|
|
191
185
|
* The section already says primary or subagent, so a mode chip only marks the
|
|
192
186
|
* both-ways `all` agent: it sits with the primaries but other agents call it
|
|
193
187
|
* too.
|
|
194
188
|
*/
|
|
195
|
-
function AgentCardBadges({ agent
|
|
189
|
+
function AgentCardBadges({ agent }) {
|
|
196
190
|
const tI18nComplete = useI18nTranslations('hardcodedUi.i18nComplete');
|
|
197
191
|
const mode = agent.mode?.toLowerCase();
|
|
198
|
-
return (_jsxs(_Fragment, { children: [mode === 'all' ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('agentsListAlsoSubagent') })) : null,
|
|
192
|
+
return (_jsxs(_Fragment, { children: [mode === 'all' ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('agentsListAlsoSubagent') })) : null, agent.enabled === false ? (_jsx(Badge, { variant: "muted", size: "xs", children: tI18nComplete.raw('text75081b593d15') })) : null] }));
|
|
199
193
|
}
|
|
200
194
|
/**
|
|
201
195
|
* Which agent a new chat in this project starts with. Rides the filter row
|
|
@@ -227,5 +221,5 @@ function DefaultAgentSelector({ projectId, config, canWrite, }) {
|
|
|
227
221
|
});
|
|
228
222
|
if (!declaresDefaultAgent || availableAgents.length === 0 || !current)
|
|
229
223
|
return null;
|
|
230
|
-
return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
224
|
+
return (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [mutation.isPending ? _jsx(Loading, { className: "size-4 shrink-0" }) : null, _jsxs(Select, { value: current, onValueChange: (agentName) => mutation.mutate(agentName), disabled: !canWrite || mutation.isPending, children: [_jsx(SelectTrigger, { "aria-label": tI18nComplete.raw('text94da52ecd6c5'), className: "h-9 w-44 shrink-0", size: "sm", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { align: "end", children: availableAgents.map((agent) => (_jsx(SelectItem, { value: agent.name, children: _jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx(AgentMark, { slug: agent.name }), _jsx("span", { className: "truncate", children: agentTitle(agent.name) })] }) }, agent.name))) })] })] }));
|
|
231
225
|
}
|
|
@@ -46,6 +46,20 @@ export type CatalogEntry = (CatalogEntryFields & {
|
|
|
46
46
|
});
|
|
47
47
|
/** Native platform provider. Each member's paired machine is one of its accounts. */
|
|
48
48
|
export declare function computersCatalogEntry(tI18nComplete: UiTranslator): CatalogEntry;
|
|
49
|
+
/**
|
|
50
|
+
* The icon URL a Discover record's card loads.
|
|
51
|
+
*
|
|
52
|
+
* A share of the catalogue's records carry a `domain` and no `icon` — the
|
|
53
|
+
* upstream feed only holds a logo for entries it could scrape one for. The
|
|
54
|
+
* card for such a record used to fall straight to a generic globe glyph, which
|
|
55
|
+
* is exactly the "inconsistent or missing imagery" a connector catalogue
|
|
56
|
+
* should not show when the app's own favicon is one well-known URL away. The
|
|
57
|
+
* same favicon service the marketing connectors page and `FaviconAvatar`
|
|
58
|
+
* already use resolves it; where the domain is a pseudo-host (`*.local`, the
|
|
59
|
+
* apis.guru feed's placeholders) it answers the generic globe image, which
|
|
60
|
+
* reads the same as the glyph it replaced.
|
|
61
|
+
*/
|
|
62
|
+
export declare function discoverEntryIcon(connector: Pick<DiscoverConnector, 'icon' | 'domain'>): string | null;
|
|
49
63
|
export declare function catalogEntryFromDiscover(connector: DiscoverConnector): CatalogEntry;
|
|
50
64
|
export declare function catalogEntryFromEasyConnect(app: PipedreamApp & {
|
|
51
65
|
provider?: 'composio' | 'pipedream';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { faviconUrlForHostname } from '../../../lib/favicon.js';
|
|
1
2
|
import { POPULAR_SECTION } from './connector-categories.js';
|
|
2
3
|
/** Native platform provider. Each member's paired machine is one of its accounts. */
|
|
3
4
|
export function computersCatalogEntry(tI18nComplete) {
|
|
@@ -12,6 +13,22 @@ export function computersCatalogEntry(tI18nComplete) {
|
|
|
12
13
|
popularity: null,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* The icon URL a Discover record's card loads.
|
|
18
|
+
*
|
|
19
|
+
* A share of the catalogue's records carry a `domain` and no `icon` — the
|
|
20
|
+
* upstream feed only holds a logo for entries it could scrape one for. The
|
|
21
|
+
* card for such a record used to fall straight to a generic globe glyph, which
|
|
22
|
+
* is exactly the "inconsistent or missing imagery" a connector catalogue
|
|
23
|
+
* should not show when the app's own favicon is one well-known URL away. The
|
|
24
|
+
* same favicon service the marketing connectors page and `FaviconAvatar`
|
|
25
|
+
* already use resolves it; where the domain is a pseudo-host (`*.local`, the
|
|
26
|
+
* apis.guru feed's placeholders) it answers the generic globe image, which
|
|
27
|
+
* reads the same as the glyph it replaced.
|
|
28
|
+
*/
|
|
29
|
+
export function discoverEntryIcon(connector) {
|
|
30
|
+
return connector.icon ?? faviconUrlForHostname(connector.domain);
|
|
31
|
+
}
|
|
15
32
|
export function catalogEntryFromDiscover(connector) {
|
|
16
33
|
return {
|
|
17
34
|
source: 'discover',
|
|
@@ -20,7 +37,7 @@ export function catalogEntryFromDiscover(connector) {
|
|
|
20
37
|
slug: connector.slug,
|
|
21
38
|
name: connector.name,
|
|
22
39
|
description: connector.description,
|
|
23
|
-
icon: connector
|
|
40
|
+
icon: discoverEntryIcon(connector),
|
|
24
41
|
categories: connector.categories,
|
|
25
42
|
popularity: connector.popularity,
|
|
26
43
|
};
|
|
@@ -10,6 +10,34 @@ export interface CatalogInstall {
|
|
|
10
10
|
pendingKey: string | null;
|
|
11
11
|
onInstall: (entry: CatalogEntry) => void;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* A catalog favicon, or a neutral glyph tile when the record has none or its
|
|
15
|
+
* URL fails to load.
|
|
16
|
+
*
|
|
17
|
+
* A plain `<img>`, not `next/image`. These are third-party favicons on
|
|
18
|
+
* arbitrary hosts, so the loader was already bypassed with `unoptimized` — which
|
|
19
|
+
* left `fill` costing an absolutely-positioned child inside a `relative`
|
|
20
|
+
* wrapper, per card, for no optimisation in return. Native `loading="lazy"`
|
|
21
|
+
* defers every icon below the fold, which is most of them on a browse page.
|
|
22
|
+
*
|
|
23
|
+
* `width`/`height` are set so the box is reserved before the image arrives and
|
|
24
|
+
* the grid never reflows around a late favicon.
|
|
25
|
+
*
|
|
26
|
+
* The broken-image state is the fallback the null case already had: a logo
|
|
27
|
+
* URL that 404s used to render the browser's broken-image glyph inside the
|
|
28
|
+
* tile, which is worse than no imagery at all. `onError` swaps to the same
|
|
29
|
+
* glyph tile — the same fallback a record with no icon gets, so "missing" and
|
|
30
|
+
* "unavailable" look identical. Mirrors `ConnectorAppIcon`'s broken state.
|
|
31
|
+
*
|
|
32
|
+
* The latch is per URL (`failedUrl`, not a boolean): a URL that failed stays
|
|
33
|
+
* failed, while a swapped URL gets a fresh try without any call-site key —
|
|
34
|
+
* merge-search swaps a merged twin's icon to the managed logo as catalogue
|
|
35
|
+
* pages stream in.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ConnectorIcon({ icon, computer, }: {
|
|
38
|
+
icon: string | null;
|
|
39
|
+
computer?: boolean;
|
|
40
|
+
}): import("react").JSX.Element;
|
|
13
41
|
/**
|
|
14
42
|
* The catalogue body: one flat, paginated, searchable grid.
|
|
15
43
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslations } from '../../../i18n/index.js';
|
|
4
4
|
import { GlobeIcon, MonitorIcon } from '@phosphor-icons/react';
|
|
5
|
-
import { memo } from 'react';
|
|
5
|
+
import { memo, useState } from 'react';
|
|
6
6
|
import { Badge } from '../../../primitives/badge.js';
|
|
7
7
|
import { Button } from '../../../primitives/button.js';
|
|
8
8
|
import Loading from '../../../primitives/loading.js';
|
|
@@ -43,7 +43,8 @@ function CatalogAffordance({ entry, connected, install, }) {
|
|
|
43
43
|
disabled: !install.ready || install.pendingKey !== null, "data-testid": "catalog-add" }));
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* A catalog favicon, or a neutral glyph tile when the record has none
|
|
46
|
+
* A catalog favicon, or a neutral glyph tile when the record has none or its
|
|
47
|
+
* URL fails to load.
|
|
47
48
|
*
|
|
48
49
|
* A plain `<img>`, not `next/image`. These are third-party favicons on
|
|
49
50
|
* arbitrary hosts, so the loader was already bypassed with `unoptimized` — which
|
|
@@ -53,12 +54,24 @@ function CatalogAffordance({ entry, connected, install, }) {
|
|
|
53
54
|
*
|
|
54
55
|
* `width`/`height` are set so the box is reserved before the image arrives and
|
|
55
56
|
* the grid never reflows around a late favicon.
|
|
57
|
+
*
|
|
58
|
+
* The broken-image state is the fallback the null case already had: a logo
|
|
59
|
+
* URL that 404s used to render the browser's broken-image glyph inside the
|
|
60
|
+
* tile, which is worse than no imagery at all. `onError` swaps to the same
|
|
61
|
+
* glyph tile — the same fallback a record with no icon gets, so "missing" and
|
|
62
|
+
* "unavailable" look identical. Mirrors `ConnectorAppIcon`'s broken state.
|
|
63
|
+
*
|
|
64
|
+
* The latch is per URL (`failedUrl`, not a boolean): a URL that failed stays
|
|
65
|
+
* failed, while a swapped URL gets a fresh try without any call-site key —
|
|
66
|
+
* merge-search swaps a merged twin's icon to the managed logo as catalogue
|
|
67
|
+
* pages stream in.
|
|
56
68
|
*/
|
|
57
|
-
function ConnectorIcon({ icon, computer = false }) {
|
|
58
|
-
|
|
69
|
+
export function ConnectorIcon({ icon, computer = false, }) {
|
|
70
|
+
const [failedUrl, setFailedUrl] = useState(null);
|
|
71
|
+
if (!icon || failedUrl === icon) {
|
|
59
72
|
return (_jsx("span", { className: "bg-card flex size-9 shrink-0 items-center justify-center rounded-sm", children: computer ? _jsx(MonitorIcon, { className: "size-5" }) : _jsx(GlobeIcon, { className: "size-5" }) }));
|
|
60
73
|
}
|
|
61
|
-
return (_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: icon, alt: "", width: 36, height: 36, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", className: "size-9 object-contain" }) }));
|
|
74
|
+
return (_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-sm", children: _jsx("img", { src: icon, alt: "", width: 36, height: 36, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", className: "size-9 object-contain", onError: () => setFailedUrl(icon) }) }));
|
|
62
75
|
}
|
|
63
76
|
/**
|
|
64
77
|
* One catalogue card: icon and title on the bare page — no border, no
|