@kortix/ui 0.14.10 → 0.14.12
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/shared/runtime-versions.d.ts +9 -0
- package/dist/browser/browser-panel.d.ts +2 -1
- package/dist/browser/browser-panel.js +12 -2
- package/dist/customize/settings/tabs/sandbox-template-form.js +8 -1
- package/dist/files/renderers/show-content-renderer.d.ts +2 -2
- package/dist/files/renderers/show-content-renderer.js +2 -1
- package/dist/i18n/en.d.ts +18 -0
- package/dist/i18n/en.js +30 -12
- package/dist/i18n/ui-catalog/remaining-ui-translation-keys.generated.js +3 -3
- package/dist/internal/branding/branding-provider.js +3 -3
- package/dist/internal/hooks/use-notification-center.d.ts +10 -6
- package/dist/internal/hooks/use-notification-center.js +32 -10
- package/dist/internal/lib/browser-noise/rules/extensions.js +3 -0
- package/dist/internal/lib/site-metadata.js +4 -4
- package/dist/internal/notifications/notification-bell.js +4 -3
- package/dist/internal/notifications/notification-rows.d.ts +1 -1
- package/dist/internal/notifications/notification-rows.js +2 -0
- package/dist/internal/stores/kortix-computer-store.d.ts +3 -2
- package/dist/internal/stores/kortix-computer-store.js +5 -3
- package/dist/session/composer/index.d.ts +1 -0
- package/dist/session/composer/index.js +1 -0
- package/dist/session/composer/send-stop-control.js +8 -3
- package/dist/session/header/session-site-header.js +3 -1
- package/dist/session/panel/easy/detail-view.js +11 -0
- package/dist/session/panel/mobile-tool-drawer.js +10 -4
- package/dist/session/panel/open-session-quick-view.js +3 -3
- package/dist/session/panel/panel-fallback.js +10 -7
- package/dist/session/panel/session-desktop.d.ts +6 -0
- package/dist/session/panel/session-desktop.js +74 -0
- package/dist/session/panel/session-panel-provider.d.ts +1 -0
- package/dist/session/panel/session-panel-provider.js +17 -1
- package/dist/session/panel/shared/use-panel-handoffs.d.ts +2 -2
- package/dist/session/panel/shared/use-panel-handoffs.js +5 -1
- package/dist/session/panel/use-session-panel-layout.js +8 -0
- package/dist/session/status/session-tab-title.d.ts +1 -1
- package/dist/session/status/session-tab-title.js +1 -1
- package/dist/session/thread/session-chat.js +12 -0
- package/dist/session/thread/session-context-sub-sessions.js +1 -1
- package/dist/session/tool/tools/image-gen-tool.d.ts +1 -1
- package/dist/session/tool/tools/image-gen-tool.js +17 -8
- package/dist/session/turn/user-message.d.ts +5 -4
- package/dist/session/turn/user-message.js +7 -5
- package/dist/styles.css +1 -1
- package/dist/workspace/session-workspace.js +24 -5
- package/package.json +3 -3
- package/skills/kortix-ui/references/modules.md +1 -1
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
* The bell (KRTX-1742): the person's notification inbox, across projects.
|
|
5
5
|
*
|
|
6
6
|
* It sits in the project sidebar header and in `AccountTopBar`, only while the
|
|
7
|
-
*
|
|
7
|
+
* inbox gate is on (`useNotificationInboxEnabled`). The badge is
|
|
8
8
|
* the server's unread count, clamped at 99+. A row opens its session (or the
|
|
9
9
|
* Triggers page for an alert) and marks itself read. When this browser shows
|
|
10
10
|
* no notifications yet, the panel's footer offers to turn them on.
|
|
@@ -33,7 +33,7 @@ import { useNotificationInbox } from '@kortix/sdk/react';
|
|
|
33
33
|
import { BellIcon, ChatCircleIcon, QuestionIcon, ShareIcon, ShieldWarningIcon, WarningIcon, XCircleIcon, XIcon, } from '@phosphor-icons/react';
|
|
34
34
|
import { useState } from 'react';
|
|
35
35
|
import { badgeCount, tabRows, tabUnreadCount, withoutNotificationParam, } from './notification-rows.js';
|
|
36
|
-
import {
|
|
36
|
+
import { useNotificationInboxEnabled } from '../hooks/use-notification-center.js';
|
|
37
37
|
import { webPushSupported } from './web-push.js';
|
|
38
38
|
/** Every kind glyph is solid: Phosphor at `weight="fill"`, the done check is `SolidCheckIcon`. */
|
|
39
39
|
const solid = (Icon) => function SolidKindIcon(props) {
|
|
@@ -51,6 +51,7 @@ const KIND_MARK = {
|
|
|
51
51
|
capture_device_down: { icon: solid(WarningIcon), className: 'text-kortix-red' },
|
|
52
52
|
capture_device_recovered: { icon: SolidCheckIcon, className: 'text-kortix-green' },
|
|
53
53
|
message: { icon: solid(ChatCircleIcon), className: 'text-muted-foreground' },
|
|
54
|
+
artifact_comment: { icon: solid(ChatCircleIcon), className: 'text-muted-foreground' },
|
|
54
55
|
};
|
|
55
56
|
/** Props only: the header, the All / Needs you / Done tabs, the rows, and the footer offer. */
|
|
56
57
|
export function NotificationPanel({ rows, state, unreadCount, prompt, onOpenRow, onMarkAllRead, onRetry, onTurnOn, onDismissPrompt, }) {
|
|
@@ -96,7 +97,7 @@ export function NotificationBell({ projectId, onNavigate, surface = 'bg-backgrou
|
|
|
96
97
|
const t = useTranslations('notifications');
|
|
97
98
|
const viewerId = useUiViewerId();
|
|
98
99
|
const [open, setOpen] = useState(false);
|
|
99
|
-
const notificationCenter =
|
|
100
|
+
const notificationCenter = useNotificationInboxEnabled(projectId);
|
|
100
101
|
const inbox = useNotificationInbox({ userId: viewerId, enabled: notificationCenter });
|
|
101
102
|
const notificationsOn = useWebNotificationStore((s) => s.preferences.enabled);
|
|
102
103
|
const permission = useWebNotificationStore((s) => s.permission);
|
|
@@ -55,7 +55,7 @@ export declare function notificationTag(row: InboxNotification): string;
|
|
|
55
55
|
* conversation (a message), its session, the Reminders page (a reminder
|
|
56
56
|
* alert), or the Triggers page.
|
|
57
57
|
*/
|
|
58
|
-
export declare function rowDestination(row: InboxNotification): 'room' | 'session' | 'reminders' | 'triggers';
|
|
58
|
+
export declare function rowDestination(row: InboxNotification): 'artifact' | 'room' | 'session' | 'reminders' | 'triggers';
|
|
59
59
|
/** The message `public/sw.js` posts to a tab already on a clicked notification's page. */
|
|
60
60
|
export declare const NOTIFICATION_OPEN_MESSAGE = "kortix:notification-open";
|
|
61
61
|
/** The notification id a service-worker message says this tab opened, or null. */
|
|
@@ -12,7 +12,7 @@ export interface ReadyChipState {
|
|
|
12
12
|
primaryName?: string;
|
|
13
13
|
}
|
|
14
14
|
/** The panel surfaces reachable from outside the panel. */
|
|
15
|
-
export type QuickView = 'terminal' | 'audit' | 'browser' | 'files' | 'reminders';
|
|
15
|
+
export type QuickView = 'terminal' | 'audit' | 'browser' | 'files' | 'reminders' | 'desktop';
|
|
16
16
|
/**
|
|
17
17
|
* Extra aim for a quick-view request — "open the browser" vs "open the browser
|
|
18
18
|
* ON THIS URL".
|
|
@@ -100,6 +100,7 @@ interface KortixComputerState {
|
|
|
100
100
|
target?: QuickViewTarget;
|
|
101
101
|
} | null;
|
|
102
102
|
mobileToolView: QuickView | null;
|
|
103
|
+
mobileToolTarget: QuickViewTarget | null;
|
|
103
104
|
openFileInComputer: (filePath: string, filePathList?: string[], targetLine?: number) => void;
|
|
104
105
|
openFileBrowser: () => void;
|
|
105
106
|
navigateToToolCall: (toolIndex: number) => void;
|
|
@@ -196,7 +197,7 @@ interface KortixComputerState {
|
|
|
196
197
|
target?: QuickViewTarget;
|
|
197
198
|
} | null;
|
|
198
199
|
/** Mobile only — open `view` in the standalone tool drawer. */
|
|
199
|
-
openMobileTool: (view: QuickView) => void;
|
|
200
|
+
openMobileTool: (view: QuickView, target?: QuickViewTarget) => void;
|
|
200
201
|
closeMobileTool: () => void;
|
|
201
202
|
reset: () => void;
|
|
202
203
|
}
|
|
@@ -29,6 +29,7 @@ const initialState = {
|
|
|
29
29
|
pendingPrimaryOpenSessionId: null,
|
|
30
30
|
pendingQuickView: null,
|
|
31
31
|
mobileToolView: null,
|
|
32
|
+
mobileToolTarget: null,
|
|
32
33
|
/** A quick-view consumed by the transient BOOT shell (e.g. Terminal clicked
|
|
33
34
|
* while the sandbox boots). The shell's provider dies at the boot→ready
|
|
34
35
|
* handoff, taking the view with it — this remembers the intent so
|
|
@@ -283,6 +284,7 @@ export const useKortixComputerStore = create()(devtools((set, get) => ({
|
|
|
283
284
|
pendingToolNavIndex: null,
|
|
284
285
|
// A tool drawer belonging to the session the user just left.
|
|
285
286
|
mobileToolView: null,
|
|
287
|
+
mobileToolTarget: null,
|
|
286
288
|
// The ⌘I restore memory dies with the session. Within a session the
|
|
287
289
|
// key is a minimise/restore pair; across one it is not, and a detail
|
|
288
290
|
// last seen on a page the user has navigated away from must never be
|
|
@@ -421,11 +423,11 @@ export const useKortixComputerStore = create()(devtools((set, get) => ({
|
|
|
421
423
|
}
|
|
422
424
|
return { view: pending.view, target: pending.target };
|
|
423
425
|
},
|
|
424
|
-
openMobileTool: (view) => {
|
|
425
|
-
set({ mobileToolView: view });
|
|
426
|
+
openMobileTool: (view, target) => {
|
|
427
|
+
set({ mobileToolView: view, mobileToolTarget: target ? { ...target } : null });
|
|
426
428
|
},
|
|
427
429
|
closeMobileTool: () => {
|
|
428
|
-
set({ mobileToolView: null });
|
|
430
|
+
set({ mobileToolView: null, mobileToolTarget: null });
|
|
429
431
|
},
|
|
430
432
|
reset: () => {
|
|
431
433
|
console.log('[KortixComputerStore] Full reset');
|
|
@@ -15,6 +15,7 @@ export { type DraftScope } from './draft/composer-draft.js';
|
|
|
15
15
|
export { ComposerEditor, type ComposerEditorHandle } from './editor/composer-editor.js';
|
|
16
16
|
export { baseExtensions } from './editor/extensions.js';
|
|
17
17
|
export { MentionNode } from './editor/mention-node.js';
|
|
18
|
+
export { MentionChip } from '../thread/mention-chip.js';
|
|
18
19
|
export { baseSuggestion, type MenuController } from './editor/suggestion.js';
|
|
19
20
|
export { ReasoningEffortSelector } from './reasoning-effort-selector.js';
|
|
20
21
|
export { SessionChatInput, type SessionChatInputProps } from './session-chat-input.js';
|
|
@@ -14,6 +14,7 @@ export { ComposerUnderbar } from './composer-underbar.js';
|
|
|
14
14
|
export { ComposerEditor } from './editor/composer-editor.js';
|
|
15
15
|
export { baseExtensions } from './editor/extensions.js';
|
|
16
16
|
export { MentionNode } from './editor/mention-node.js';
|
|
17
|
+
export { MentionChip } from '../thread/mention-chip.js';
|
|
17
18
|
export { baseSuggestion } from './editor/suggestion.js';
|
|
18
19
|
export { ReasoningEffortSelector } from './reasoning-effort-selector.js';
|
|
19
20
|
export { SessionChatInput } from './session-chat-input.js';
|
|
@@ -4,6 +4,7 @@ import { Button } from '../../primitives/button.js';
|
|
|
4
4
|
import Hint from '../../primitives/hint.js';
|
|
5
5
|
import { Kbd } from '../../primitives/kbd.js';
|
|
6
6
|
import Loading from '../../primitives/loading.js';
|
|
7
|
+
import { cn } from '../../lib/utils.js';
|
|
7
8
|
import { ArrowUpIcon as ArrowUp, SquareIcon } from '@phosphor-icons/react';
|
|
8
9
|
import { AnimatePresence, m } from 'motion/react';
|
|
9
10
|
import { useTranslations } from '../../i18n/index.js';
|
|
@@ -24,6 +25,7 @@ const ICON_SWAP = {
|
|
|
24
25
|
export function SendStopControl({ isSending, isBusy, onStop, stopDisabled, canStop = true, escCount, lockForQuestion, questionButtonLabel, submitLabel = null, questionCanAct, hasText, canSubmit, submitDisabled, disabled, modelUnavailable, agentUnavailable = false, attachmentFailed = false, attachmentUnsupported = null, onSubmit, }) {
|
|
25
26
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
26
27
|
const t = useTranslations('threads');
|
|
28
|
+
const tDesktop = useTranslations('sessionDesktop');
|
|
27
29
|
const tAttachments = useTranslations('hardcodedUi.composerAttachments');
|
|
28
30
|
// One line, the same one the agent picker's tooltip carries — the two
|
|
29
31
|
// controls are refusing for one reason and must not word it two ways.
|
|
@@ -54,9 +56,12 @@ export function SendStopControl({ isSending, isBusy, onStop, stopDisabled, canSt
|
|
|
54
56
|
// alone, a click meant to send it ended the run and left it unsent. Send
|
|
55
57
|
// goes out the way Enter does; Stop steps back to secondary beside it.
|
|
56
58
|
const followUp = hasText || canSubmit;
|
|
57
|
-
const stop = (_jsxs("div", { className: "relative flex items-center", children: [escCount > 0 && (
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const stop = (_jsxs("div", { className: "relative flex items-center", children: [escCount > 0 && (
|
|
60
|
+
// Progress toward the triple-ESC stop: one pip per press, so the
|
|
61
|
+
// count reads at a glance instead of as a bare "×2".
|
|
62
|
+
_jsx("div", { role: "status", className: "animate-in fade-in-0 zoom-in-95 slide-in-from-bottom-2 pointer-events-none absolute right-1/2 bottom-full mb-2 translate-x-1/2 duration-(--duration-normal)", children: _jsxs("div", { className: "bg-background text-foreground inline-flex w-fit items-center gap-2 rounded-md border px-2 py-1 text-xs whitespace-nowrap", children: [_jsx("span", { className: "flex items-center gap-1", "aria-hidden": true, children: [0, 1, 2].map((pip) => (_jsx("span", { className: cn('size-1.5 rounded-full', pip < escCount ? 'bg-foreground' : 'bg-muted-foreground/30') }, pip))) }), _jsxs("span", { className: "flex items-center gap-1", children: [_jsx(Kbd, { className: "h-4 w-auto", children: tDesktop('escape') }), _jsx("span", { className: "tabular-nums", children: escCount === 1
|
|
63
|
+
? tI18nComplete.raw('textb9ef397631ca')
|
|
64
|
+
: tI18nComplete.raw('text9754e2d14f9e') })] })] }) })), _jsx(Hint, { side: "top", label: _jsxs("span", { className: "flex items-center gap-2", children: [tI18nComplete.raw('textcae7d57bc067'), _jsxs("span", { className: "flex items-center gap-1 opacity-60", children: [_jsx(Kbd, { className: "h-4 w-auto", children: tDesktop('escape') }), _jsx("span", { className: "tabular-nums", children: "\u00D73" })] })] }), children: _jsx(Button, { size: "icon-base", variant: followUp ? 'secondary' : 'default', "aria-label": t('stop'), onClick: onStop, disabled: stopDisabled || !onStop, className: ICON_BUTTON, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(m.span, { className: "flex items-center", ...ICON_SWAP, children: _jsx(SquareIcon, { weight: "fill", className: "size-4 shrink-0" }) }, "stop") }) }) })] }));
|
|
60
65
|
if (!followUp)
|
|
61
66
|
return stop;
|
|
62
67
|
return (_jsxs("div", { className: "flex items-center gap-1.5", children: [stop, sendButton] }));
|
|
@@ -42,7 +42,7 @@ import { useSessionHistoryViewStore } from '../../internal/stores/session-histor
|
|
|
42
42
|
import { useIsActionPanelOpen, useReadyChip, useToggleActionPanel, } from '../../internal/stores/kortix-computer-store.js';
|
|
43
43
|
import { createProjectSession, directSubsessions, restartProjectSession, stopProjectSession } from '@kortix/sdk';
|
|
44
44
|
import { qk, useProjectSession, useRuntimeSupports, useSessionParticipants, useSessionWatch, } from '@kortix/sdk/react';
|
|
45
|
-
import { ArchiveIcon, ArrowsClockwiseIcon, BellIcon, BellSlashIcon, CaretDoubleLeftIcon, GitForkIcon, LinkSimpleIcon, CaretDownIcon, CodeSimpleIcon as Code2, FileArrowDownIcon as FileDown, FolderSimpleIcon, GlobeSimpleIcon, StackIcon as Layers, PencilSimpleIcon, PushPinIcon, PushPinSlashIcon, ArrowCounterClockwiseIcon as RotateCcw, ShareIcon as Share, SquareIcon as Square, TerminalIcon, TrashIcon, } from '@phosphor-icons/react';
|
|
45
|
+
import { ArchiveIcon, ArrowsClockwiseIcon, BellIcon, BellSlashIcon, CaretDoubleLeftIcon, GitForkIcon, LinkSimpleIcon, CaretDownIcon, CodeSimpleIcon as Code2, FileArrowDownIcon as FileDown, FolderSimpleIcon, GlobeSimpleIcon, MonitorIcon, StackIcon as Layers, PencilSimpleIcon, PushPinIcon, PushPinSlashIcon, ArrowCounterClockwiseIcon as RotateCcw, ShareIcon as Share, SquareIcon as Square, TerminalIcon, TrashIcon, } from '@phosphor-icons/react';
|
|
46
46
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
47
47
|
import { useMemo, useRef, useState } from 'react';
|
|
48
48
|
/** Sandbox surfaces reachable from the header. One list drives both the
|
|
@@ -51,6 +51,8 @@ import { useMemo, useRef, useState } from 'react';
|
|
|
51
51
|
const DEV_TOOLS = [
|
|
52
52
|
{ view: 'terminal', label: 'Terminal', Icon: TerminalIcon },
|
|
53
53
|
{ view: 'browser', label: 'Browser', Icon: GlobeSimpleIcon },
|
|
54
|
+
// The computer's live screen (`session-desktop.tsx`).
|
|
55
|
+
{ view: 'desktop', label: 'Desktop', Icon: MonitorIcon },
|
|
54
56
|
{ view: 'files', label: 'Files', Icon: FolderSimpleIcon },
|
|
55
57
|
];
|
|
56
58
|
export function SessionSiteHeader({ sessionId, sessionTitle, isMobileView, leadingAction, parent, }) {
|
|
@@ -22,6 +22,7 @@ import { ButtonGroup } from '../../../primitives/button-group.js';
|
|
|
22
22
|
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '../../../primitives/drawer.js';
|
|
23
23
|
import { isPlanWriteTool } from '../../turn/plan-anchor.js';
|
|
24
24
|
import { cn } from '../../../lib/utils.js';
|
|
25
|
+
import { hasOpenFloatingLayer } from '../../../lib/z-stack.js';
|
|
25
26
|
import { focusWithoutScroll } from '../../../internal/lib/utils/focus-without-scroll.js';
|
|
26
27
|
import { CaretLeftIcon as ChevronLeft, CaretRightIcon as ChevronRight, } from '@phosphor-icons/react';
|
|
27
28
|
import { AnimatePresence, m, useReducedMotion, } from 'motion/react';
|
|
@@ -116,6 +117,16 @@ function useDetailKeyboard(detail, isMobile, onBack, detailRef) {
|
|
|
116
117
|
if (isTypingTarget(event.target))
|
|
117
118
|
return;
|
|
118
119
|
if (event.key === 'Escape') {
|
|
120
|
+
if (event.defaultPrevented || hasOpenFloatingLayer())
|
|
121
|
+
return;
|
|
122
|
+
const dialog = detailRef.current;
|
|
123
|
+
const coveringDialog = Array.from(document.querySelectorAll('[role="dialog"][data-state="open"],[role="alertdialog"][data-state="open"]')).some((layer) => !dialog || !layer.contains(dialog));
|
|
124
|
+
if (coveringDialog)
|
|
125
|
+
return;
|
|
126
|
+
// Mark the press consumed: the page-wide triple-ESC-to-stop
|
|
127
|
+
// (`session-chat.tsx`) skips a `defaultPrevented` Escape. Without it,
|
|
128
|
+
// closing three full-screen images in a row stopped a running turn.
|
|
129
|
+
event.preventDefault();
|
|
119
130
|
onBack();
|
|
120
131
|
return;
|
|
121
132
|
}
|
|
@@ -19,9 +19,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
19
19
|
*/
|
|
20
20
|
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '../../primitives/drawer.js';
|
|
21
21
|
import { BrowserPanel } from '../../browser/browser-panel.js';
|
|
22
|
+
import { ArtifactPanelPreview } from '../../artifacts/artifact-session.js';
|
|
23
|
+
import { parseArtifactUrl } from '../../artifacts/frame-policy.js';
|
|
22
24
|
import { CloseButton } from './easy/detail-view.js';
|
|
23
25
|
import { SessionAuditPanel } from './session-audit-panel.js';
|
|
24
26
|
import { SessionRemindersPanel } from './session-reminders-panel.js';
|
|
27
|
+
import { SessionDesktop } from './session-desktop.js';
|
|
25
28
|
import { SessionFilesExplorer } from './session-files-explorer.js';
|
|
26
29
|
import { SessionTerminalPanel } from '../../terminal/session-terminal-panel.js';
|
|
27
30
|
import { SessionBootStatus } from '../status/session-starting-loader.js';
|
|
@@ -29,22 +32,25 @@ import { useOptionalSessionPanel } from './session-panel-provider.js';
|
|
|
29
32
|
import { useLocalizedUiCatalog } from '../../i18n/ui-catalog/use-localized-ui-catalog.js';
|
|
30
33
|
import { useKortixComputerStore } from '../../internal/stores/kortix-computer-store.js';
|
|
31
34
|
import { sessionPreviewTabId } from '../../internal/stores/session-browser-store.js';
|
|
32
|
-
import { AlarmIcon, FolderOpenIcon, GlobeIcon, ScrollIcon, TerminalWindowIcon, } from '@phosphor-icons/react';
|
|
35
|
+
import { AlarmIcon, FolderOpenIcon, GlobeIcon, MonitorIcon, ScrollIcon, TerminalWindowIcon, } from '@phosphor-icons/react';
|
|
33
36
|
const TOOL_META = {
|
|
34
37
|
terminal: { label: 'Terminal', Icon: TerminalWindowIcon },
|
|
35
38
|
browser: { label: 'Browser', Icon: GlobeIcon },
|
|
36
39
|
files: { label: 'Files', Icon: FolderOpenIcon },
|
|
37
40
|
audit: { label: 'Audit', Icon: ScrollIcon },
|
|
38
41
|
reminders: { label: 'Reminders', Icon: AlarmIcon },
|
|
42
|
+
desktop: { label: 'Desktop', Icon: MonitorIcon },
|
|
39
43
|
};
|
|
40
44
|
export function MobileToolDrawer({ sessionId, projectId, projectSessionId, }) {
|
|
41
45
|
const view = useKortixComputerStore((s) => s.mobileToolView);
|
|
46
|
+
const target = useKortixComputerStore((s) => s.mobileToolTarget);
|
|
42
47
|
const closeMobileTool = useKortixComputerStore((s) => s.closeMobileTool);
|
|
43
48
|
const toolMeta = useLocalizedUiCatalog(TOOL_META);
|
|
44
49
|
const meta = view ? toolMeta[view] : null;
|
|
50
|
+
const artifactUrl = view === 'browser' && target?.url && parseArtifactUrl(target.url) ? target.url : null;
|
|
45
51
|
// Terminal, Browser and Files need the computer: while it boots, the drawer
|
|
46
52
|
// shows the wake status in their place.
|
|
47
|
-
const booting = !!useOptionalSessionPanel()?.booting &&
|
|
48
|
-
(view === 'terminal' || view === 'browser' || view === 'files');
|
|
49
|
-
return (_jsx(Drawer, { open: view !== null, onOpenChange: (next) => !next && closeMobileTool(), children: _jsxs(DrawerContent, { className: "flex h-[95dvh] max-h-[95dvh] min-h-[95dvh] flex-col overflow-hidden p-0", children: [_jsx(DrawerHeader, { className: "shrink-0 px-4 py-3 text-left", children: _jsxs(DrawerTitle, { className: "flex items-center justify-between gap-2 text-base", children: [_jsxs("span", { className: "flex items-center gap-2.5", children: [meta && _jsx(meta.Icon, { className: "text-muted-foreground size-4 shrink-0" }), meta?.label] }), _jsx(CloseButton, { onClose: closeMobileTool, variant: "ghost" })] }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [booting && _jsx(SessionBootStatus, { projectId: projectId, sessionId: projectSessionId }), !booting && view === 'terminal' && (_jsx(SessionTerminalPanel, { sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), !booting && view === 'browser' && (_jsx(BrowserPanel, { tabId: sessionPreviewTabId(sessionId), projectId: projectId, projectSessionId: projectSessionId })), !booting && view === 'files' && (_jsx(SessionFilesExplorer, { chatSessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), view === 'audit' && (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId })), view === 'reminders' && (_jsx("div", { className: "h-full overflow-y-auto p-4", children: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }) }))] })] }) }));
|
|
53
|
+
const booting = !!useOptionalSessionPanel()?.booting && !artifactUrl &&
|
|
54
|
+
(view === 'terminal' || view === 'browser' || view === 'files' || view === 'desktop');
|
|
55
|
+
return (_jsx(Drawer, { open: view !== null, onOpenChange: (next) => !next && closeMobileTool(), children: _jsxs(DrawerContent, { className: "flex h-[95dvh] max-h-[95dvh] min-h-[95dvh] flex-col overflow-hidden p-0", children: [_jsx(DrawerHeader, { className: "shrink-0 px-4 py-3 text-left", children: _jsxs(DrawerTitle, { className: "flex items-center justify-between gap-2 text-base", children: [_jsxs("span", { className: "flex items-center gap-2.5", children: [meta && _jsx(meta.Icon, { className: "text-muted-foreground size-4 shrink-0" }), meta?.label] }), _jsx(CloseButton, { onClose: closeMobileTool, variant: "ghost" })] }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [booting && _jsx(SessionBootStatus, { projectId: projectId, sessionId: projectSessionId }), !booting && view === 'terminal' && (_jsx(SessionTerminalPanel, { sessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), artifactUrl && _jsx(ArtifactPanelPreview, { url: artifactUrl, onClose: closeMobileTool }, artifactUrl), !booting && view === 'browser' && !artifactUrl && (_jsx(BrowserPanel, { tabId: sessionPreviewTabId(sessionId), projectId: projectId, projectSessionId: projectSessionId, targetUrl: target?.url })), !booting && view === 'files' && (_jsx(SessionFilesExplorer, { chatSessionId: sessionId, projectId: projectId, projectSessionId: projectSessionId })), !booting && view === 'desktop' && (_jsx(SessionDesktop, { projectId: projectId, projectSessionId: projectSessionId, onClose: closeMobileTool })), view === 'audit' && (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId })), view === 'reminders' && (_jsx("div", { className: "h-full overflow-y-auto p-4", children: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }) }))] })] }) }));
|
|
50
56
|
}
|
|
@@ -43,7 +43,7 @@ target) {
|
|
|
43
43
|
// for behind the close button. The panel toggle still opens the panel;
|
|
44
44
|
// this path is only the "show me this one tool" intent.
|
|
45
45
|
if (isMobileViewport()) {
|
|
46
|
-
useKortixComputerStore.getState().openMobileTool(view);
|
|
46
|
+
useKortixComputerStore.getState().openMobileTool(view, target);
|
|
47
47
|
track('panel_opened', { source, surface: 'mobile-tool', view });
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
@@ -55,8 +55,8 @@ target) {
|
|
|
55
55
|
// is tab-gated — relying on the latter silently dropped the terminal open
|
|
56
56
|
// on /projects/:id/sessions/:id.
|
|
57
57
|
const activePanelSessionId = getActivePanelSessionId();
|
|
58
|
-
// Reminders
|
|
59
|
-
if (panelMode === 'advanced' && view !== 'reminders') {
|
|
58
|
+
// Reminders and Desktop are Easy details with no Advanced tab: they take the request path in both modes.
|
|
59
|
+
if (panelMode === 'advanced' && view !== 'reminders' && view !== 'desktop') {
|
|
60
60
|
if (activePanelSessionId) {
|
|
61
61
|
// `SessionPanelView` calls the file explorer 'explorer'; its 'files'
|
|
62
62
|
// member is the git-changes diff view. The quick-view vocabulary uses
|
|
@@ -26,6 +26,7 @@ const AppPreview = lazy(() => import('./easy/app-preview.js').then((m) => ({ def
|
|
|
26
26
|
const SessionTerminalPanel = lazy(() => import('../../terminal/session-terminal-panel.js').then((m) => ({ default: m.SessionTerminalPanel })));
|
|
27
27
|
const SessionFilesExplorer = lazy(() => import('./session-files-explorer.js').then((m) => ({ default: m.SessionFilesExplorer })));
|
|
28
28
|
const SessionAuditPanel = lazy(() => import('./session-audit-panel.js').then((m) => ({ default: m.SessionAuditPanel })));
|
|
29
|
+
const SessionDesktop = lazy(() => import('./session-desktop.js').then((m) => ({ default: m.SessionDesktop })));
|
|
29
30
|
const SessionRemindersPanel = lazy(() => import('./session-reminders-panel.js').then((m) => ({ default: m.SessionRemindersPanel })));
|
|
30
31
|
export function SessionPanelFallback({ projectId, projectSessionId, runtimeSessionId, }) {
|
|
31
32
|
useEffect(() => registerPanelFallbackHost(), []);
|
|
@@ -49,15 +50,17 @@ function PanelViewModal({ projectId, projectSessionId, runtimeSessionId }) {
|
|
|
49
50
|
? tI18nComplete.raw('textbb6aea287396')
|
|
50
51
|
: view === 'reminders'
|
|
51
52
|
? tReminders('title')
|
|
52
|
-
:
|
|
53
|
-
? '
|
|
54
|
-
:
|
|
55
|
-
|
|
53
|
+
: view === 'desktop'
|
|
54
|
+
? 'Desktop'
|
|
55
|
+
: target?.changes
|
|
56
|
+
? 'Changes'
|
|
57
|
+
: 'Files';
|
|
58
|
+
const body = view === 'browser' ? (_jsx(AppPreview, { url: target?.url ?? '', name: title, projectId: projectId, shareContext: { projectId, sessionId: projectSessionId }, onClose: closePanelFallbackView })) : view === 'desktop' ? (_jsx(SessionDesktop, { projectId: projectId, projectSessionId: projectSessionId, onClose: closePanelFallbackView })) : view === 'audit' ? (_jsx(SessionAuditPanel, { projectId: projectId, projectSessionId: projectSessionId })) : view === 'reminders' ? (_jsx("div", { className: "p-4", children: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }) })) : !runtimeSessionId ? (_jsx(Centered, { children: _jsx(Loading, { className: "size-5" }) })) : view === 'terminal' ? (_jsx(SessionTerminalPanel, { sessionId: runtimeSessionId, projectId: projectId, projectSessionId: projectSessionId })) : (_jsx(SessionFilesExplorer, { chatSessionId: runtimeSessionId, projectId: projectId, projectSessionId: projectSessionId, ephemeral: true, initialMode: target?.changes ? 'changes' : 'files' }));
|
|
56
59
|
return (_jsx(Modal, { open: true, onOpenChange: (open) => !open && closePanelFallbackView(), children: _jsxs(ModalContent
|
|
57
|
-
// The browser
|
|
60
|
+
// The browser and the desktop have their own close button in their toolbar.
|
|
58
61
|
, {
|
|
59
|
-
// The browser
|
|
60
|
-
showCloseButton: view !== 'browser',
|
|
62
|
+
// The browser and the desktop have their own close button in their toolbar.
|
|
63
|
+
showCloseButton: view !== 'browser' && view !== 'desktop',
|
|
61
64
|
// A single full-bleed pane: no stacked header/body/footer spacing.
|
|
62
65
|
className: "flex h-[85vh] flex-col gap-0 space-y-0 overflow-hidden p-0 lg:max-h-[85vh] lg:max-w-5xl", "data-slot": "session-panel-fallback", children: [_jsx(VisuallyHidden, { children: _jsx(ModalTitle, { children: title }) }), _jsx("div", { className: "relative min-h-0 flex-1 overflow-hidden", children: _jsx(Suspense, { fallback: _jsx(Centered, { children: _jsx(Loading, { className: "size-5" }) }), children: body }) })] }) }));
|
|
63
66
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* `SessionDesktop` — the session computer's screen, live.
|
|
5
|
+
*
|
|
6
|
+
* Every session image runs a desktop that voxl streams on port 6901
|
|
7
|
+
* (`packages/shared/src/sandbox/desktop.ts`). This frames voxl's own web
|
|
8
|
+
* client through the sandbox proxy, the same way `AppPreview` frames a port.
|
|
9
|
+
*
|
|
10
|
+
* Control is explicit, and the screen says who has it. Kortix has it by
|
|
11
|
+
* default: a transparent shield takes every pointer and key, so a user who
|
|
12
|
+
* scrolls or clicks past the panel never moves the agent's mouse. "Take over"
|
|
13
|
+
* (or a click on the screen) drops the shield, rings the screen and focuses
|
|
14
|
+
* it; "Return control" puts the shield back.
|
|
15
|
+
*
|
|
16
|
+
* The frame unmounts while this browser tab is hidden. voxl paces every
|
|
17
|
+
* viewer to the slowest one, and a background tab stops acking frames, so a
|
|
18
|
+
* forgotten tab would slow the stream for everyone watching. It reconnects,
|
|
19
|
+
* on a fresh keyframe, when the tab is shown again.
|
|
20
|
+
*/
|
|
21
|
+
import { useTranslations } from '../../i18n/index.js';
|
|
22
|
+
import { Button } from '../../primitives/button.js';
|
|
23
|
+
import { ButtonGroup } from '../../primitives/button-group.js';
|
|
24
|
+
import Loading from '../../primitives/loading.js';
|
|
25
|
+
import { useSandboxProxy } from '../../internal/hooks/use-sandbox-proxy.js';
|
|
26
|
+
import { usePreviewFrameUrl } from '../../internal/hooks/use-preview-frame-url.js';
|
|
27
|
+
import { useIsMobile } from '../../internal/hooks/index.js';
|
|
28
|
+
import { framePolicy } from '../../files/viewer/preview-policy.js';
|
|
29
|
+
import { PanelWidthButton } from '../../files/viewer/viewer-actions.js';
|
|
30
|
+
import { CloseButton, DetailSidebarToggle } from '../../files/viewer/viewer-chrome.js';
|
|
31
|
+
import { cn } from '../../lib/utils.js';
|
|
32
|
+
import { probePreviewPort } from '@kortix/sdk';
|
|
33
|
+
import { ArrowSquareOutIcon, MonitorIcon } from '@phosphor-icons/react';
|
|
34
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
35
|
+
export const DESKTOP_URL = 'http://localhost:6901/';
|
|
36
|
+
const subscribeVisibility = (onChange) => {
|
|
37
|
+
document.addEventListener('visibilitychange', onChange);
|
|
38
|
+
return () => document.removeEventListener('visibilitychange', onChange);
|
|
39
|
+
};
|
|
40
|
+
const tabVisible = () => document.visibilityState === 'visible';
|
|
41
|
+
export function SessionDesktop({ projectId, projectSessionId, onClose, }) {
|
|
42
|
+
const t = useTranslations('sessionDesktop');
|
|
43
|
+
const isMobile = useIsMobile();
|
|
44
|
+
const { proxyUrl } = useSandboxProxy();
|
|
45
|
+
const src = usePreviewFrameUrl(DESKTOP_URL, proxyUrl(DESKTOP_URL) ?? DESKTOP_URL, {
|
|
46
|
+
projectId,
|
|
47
|
+
sessionId: projectSessionId,
|
|
48
|
+
}).url;
|
|
49
|
+
const frameRef = useRef(null);
|
|
50
|
+
const [control, setControl] = useState(false);
|
|
51
|
+
// 'off' = the proxy answered 502/503/504: nothing listens on 6901.
|
|
52
|
+
const [state, setState] = useState('probing');
|
|
53
|
+
const [attempt, setAttempt] = useState(0);
|
|
54
|
+
const visible = useSyncExternalStore(subscribeVisibility, tabVisible, () => true);
|
|
55
|
+
const live = !!src && state === 'on';
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!src)
|
|
58
|
+
return;
|
|
59
|
+
const abort = new AbortController();
|
|
60
|
+
void probePreviewPort(src, { signal: abort.signal }).then((probe) => {
|
|
61
|
+
if (!abort.signal.aborted)
|
|
62
|
+
setState(probe === 'unreachable' ? 'off' : 'on');
|
|
63
|
+
});
|
|
64
|
+
return () => abort.abort();
|
|
65
|
+
}, [src, attempt]);
|
|
66
|
+
const takeOver = () => {
|
|
67
|
+
setControl(true);
|
|
68
|
+
frameRef.current?.focus();
|
|
69
|
+
};
|
|
70
|
+
return (_jsxs("div", { className: "bg-background flex h-full min-h-0 min-w-0 flex-col", children: [_jsxs("div", { className: "flex shrink-0 items-center gap-1 px-2 py-1", children: [_jsx(DetailSidebarToggle, { className: "size-7" }), _jsx(MonitorIcon, { className: "text-muted-foreground ml-1 size-3.5 shrink-0" }), _jsx("span", { className: "truncate text-sm font-medium", children: t('title') }), _jsx("div", { className: "flex-1" }), _jsxs(ButtonGroup, { className: "shrink-0", children: [_jsx(Button, { variant: "secondary", size: "icon", className: "size-7", disabled: !live, "aria-label": t('openTab'), onClick: () => src && window.open(src, '_blank', 'noopener,noreferrer'), children: _jsx(ArrowSquareOutIcon, { className: "size-3.5" }) }), _jsx(PanelWidthButton, { isMobile: isMobile }), _jsx(CloseButton, { onClose: onClose })] })] }), _jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2 px-3 pb-3", children: [_jsx("div", { "data-slot": "desktop-screen", "data-control": control ? 'user' : 'agent', className: cn('bg-muted relative min-h-0 flex-1 overflow-hidden rounded-xl ring-1 ring-border transition-shadow', control && 'ring-2 ring-primary'), children: state === 'off' ? (_jsxs("div", { className: "text-muted-foreground flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-sm", children: [_jsx(MonitorIcon, { className: "size-10 opacity-20" }), _jsx("p", { children: t('offline') }), _jsxs("p", { className: "text-xs", children: [t('start'), " ", _jsx("code", { children: "kortix-desktop start" }), "."] }), _jsx(Button, { variant: "secondary", size: "sm", onClick: () => {
|
|
71
|
+
setState('probing');
|
|
72
|
+
setAttempt((n) => n + 1);
|
|
73
|
+
}, children: t('retry') })] })) : !live || !visible ? (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsx(Loading, { className: "size-4" }) })) : (_jsxs(_Fragment, { children: [_jsx("iframe", { ref: frameRef, src: src, title: t('title'), className: "h-full w-full border-0", sandbox: framePolicy('app', src).sandbox, allow: "clipboard-read; clipboard-write; autoplay; fullscreen", tabIndex: control ? 0 : -1 }), !control && (_jsx("button", { type: "button", "aria-label": t('takeOver'), "data-slot": "desktop-shield", className: "absolute inset-0 cursor-pointer", onClick: takeOver }))] })) }), _jsx("div", { className: "flex h-9 shrink-0 items-center justify-center gap-3 text-sm", children: live && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-muted-foreground", children: control ? t('userControl') : t('agentControl') }), _jsx(Button, { variant: control ? 'secondary' : 'default', size: "sm", className: "h-7 rounded-full px-3", "aria-pressed": control, onClick: control ? () => setControl(false) : takeOver, children: control ? t('returnControl') : t('takeOver') })] })) })] })] }));
|
|
74
|
+
}
|
|
@@ -29,6 +29,7 @@ import { useTranslations } from '../../i18n/index.js';
|
|
|
29
29
|
*/
|
|
30
30
|
import { SessionAuditPanel } from './session-audit-panel.js';
|
|
31
31
|
import { SessionRemindersPanel } from './session-reminders-panel.js';
|
|
32
|
+
import { SessionDesktop } from './session-desktop.js';
|
|
32
33
|
import { SessionFilesBooting, SessionFilesExplorer } from './session-files-explorer.js';
|
|
33
34
|
import { SessionBootStatus } from '../status/session-starting-loader.js';
|
|
34
35
|
import { useSandboxProxy } from '../../internal/hooks/use-sandbox-proxy.js';
|
|
@@ -83,6 +84,7 @@ export function SessionBootGate({ boot, children }) {
|
|
|
83
84
|
}
|
|
84
85
|
export function SessionPanelProvider({ sessionId, isSessionBusy = false, projectId, projectSessionId, booting = false, children, }) {
|
|
85
86
|
const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
|
|
87
|
+
const tDesktop = useTranslations('sessionDesktop');
|
|
86
88
|
const tReminders = useTranslations('reminders');
|
|
87
89
|
const { steps, outputs, context, apps, files, isRunning, outcome } = usePanelDerivations({
|
|
88
90
|
sessionId, projectId, projectSessionId, isSessionBusy,
|
|
@@ -409,6 +411,18 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
|
|
|
409
411
|
body: _jsx(SessionRemindersPanel, { projectId: projectId, projectSessionId: projectSessionId }),
|
|
410
412
|
});
|
|
411
413
|
}, [openDetail, projectId, projectSessionId, tReminders]);
|
|
414
|
+
/** The session computer's live screen (`SessionDesktop`), at the wide split
|
|
415
|
+
* a presentation gets: the stream is a 16:10 screen, not a text column. */
|
|
416
|
+
const openDesktop = useCallback(() => {
|
|
417
|
+
openDetail({
|
|
418
|
+
key: 'desktop',
|
|
419
|
+
title: tDesktop('title'),
|
|
420
|
+
hideHeader: true,
|
|
421
|
+
padded: false,
|
|
422
|
+
body: (_jsx(SessionBootGate, { boot: _jsx(SessionBootStatus, { projectId: projectId, sessionId: projectSessionId }), children: _jsx(SessionDesktop, { projectId: projectId, projectSessionId: projectSessionId, onClose: closeDetail }) })),
|
|
423
|
+
});
|
|
424
|
+
setPanelSplit(70);
|
|
425
|
+
}, [openDetail, closeDetail, setPanelSplit, projectId, projectSessionId, tDesktop]);
|
|
412
426
|
/**
|
|
413
427
|
* The opt-in File Explorer (Marko's ask). Never a default view and never a
|
|
414
428
|
* tab — it opens only when asked for, exactly like Terminal and Audit, so
|
|
@@ -451,7 +465,7 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
|
|
|
451
465
|
}, [apps, handleOpenOutput]);
|
|
452
466
|
const { outputsDefaultOpen, pendingQuickView } = usePanelHandoffs({
|
|
453
467
|
sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps,
|
|
454
|
-
detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openBrowser, openFiles,
|
|
468
|
+
detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openDesktop, openBrowser, openFiles,
|
|
455
469
|
});
|
|
456
470
|
// The panel comes back with the session: what it showed is remembered while
|
|
457
471
|
// it is open, and reopened on the way back while the session still runs.
|
|
@@ -584,6 +598,7 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
|
|
|
584
598
|
openFiles,
|
|
585
599
|
openAudit,
|
|
586
600
|
openReminders,
|
|
601
|
+
openDesktop,
|
|
587
602
|
}), [
|
|
588
603
|
sessionId,
|
|
589
604
|
projectId,
|
|
@@ -605,6 +620,7 @@ export function SessionPanelProvider({ sessionId, isSessionBusy = false, project
|
|
|
605
620
|
openFiles,
|
|
606
621
|
openAudit,
|
|
607
622
|
openReminders,
|
|
623
|
+
openDesktop,
|
|
608
624
|
]);
|
|
609
625
|
return (_jsx(SessionPanelContext.Provider, { value: value, children: _jsx(OutputPathsContext.Provider, { value: outputPaths, children: children }) }));
|
|
610
626
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SessionPanelValue } from '../session-panel-provider.js';
|
|
2
2
|
import type { usePanelDerivations } from './use-panel-derivations.js';
|
|
3
|
-
type Handoffs = Pick<SessionPanelValue, 'sessionId' | 'projectId' | 'projectSessionId' | 'detail' | 'handleOpenOutput' | 'openDetail' | 'openTerminal' | 'openAudit' | 'openReminders' | 'openBrowser' | 'openFiles'> & Pick<ReturnType<typeof usePanelDerivations>, 'isRunning' | 'outcome' | 'apps' | 'files' | 'steps'>;
|
|
4
|
-
export declare function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openBrowser, openFiles, }: Handoffs): {
|
|
3
|
+
type Handoffs = Pick<SessionPanelValue, 'sessionId' | 'projectId' | 'projectSessionId' | 'detail' | 'handleOpenOutput' | 'openDetail' | 'openTerminal' | 'openAudit' | 'openReminders' | 'openDesktop' | 'openBrowser' | 'openFiles'> & Pick<ReturnType<typeof usePanelDerivations>, 'isRunning' | 'outcome' | 'apps' | 'files' | 'steps'>;
|
|
4
|
+
export declare function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openDesktop, openBrowser, openFiles, }: Handoffs): {
|
|
5
5
|
outputsDefaultOpen: boolean;
|
|
6
6
|
pendingQuickView: {
|
|
7
7
|
sessionId: string;
|
|
@@ -6,7 +6,7 @@ import { pathOutput, shouldAutoExpandOutputs, shouldAutoOpenPayoff, stepForCallI
|
|
|
6
6
|
import { StepDetailBody } from '../easy/step-detail-body.js';
|
|
7
7
|
import { StepIcon } from '../easy/step-icon.js';
|
|
8
8
|
import { selectPrimaryDeliverable } from './output-priority.js';
|
|
9
|
-
export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openBrowser, openFiles, }) {
|
|
9
|
+
export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRunning, outcome, apps, files, steps, detail, handleOpenOutput, openDetail, openTerminal, openAudit, openReminders, openDesktop, openBrowser, openFiles, }) {
|
|
10
10
|
// Auto-expand Outputs the moment a run finishes with something to show —
|
|
11
11
|
// never on every render of an already-finished (or still-running) run, and
|
|
12
12
|
// never on a failed one: a pile of half-written files is not a celebration.
|
|
@@ -153,6 +153,9 @@ export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRun
|
|
|
153
153
|
else if (view === 'reminders' && projectId && projectSessionId) {
|
|
154
154
|
openReminders();
|
|
155
155
|
}
|
|
156
|
+
else if (view === 'desktop') {
|
|
157
|
+
openDesktop();
|
|
158
|
+
}
|
|
156
159
|
else if (view === 'browser') {
|
|
157
160
|
openBrowser(target);
|
|
158
161
|
}
|
|
@@ -167,6 +170,7 @@ export function usePanelHandoffs({ sessionId, projectId, projectSessionId, isRun
|
|
|
167
170
|
openTerminal,
|
|
168
171
|
openAudit,
|
|
169
172
|
openReminders,
|
|
173
|
+
openDesktop,
|
|
170
174
|
openBrowser,
|
|
171
175
|
openFiles,
|
|
172
176
|
]);
|
|
@@ -157,6 +157,14 @@ function usePanelGlide(input) {
|
|
|
157
157
|
prevAspectRef.current = panelAspect;
|
|
158
158
|
prevSideSizeRef.current = sideSize;
|
|
159
159
|
const skipAnimation = consumeSkipNextExpandAnimation();
|
|
160
|
+
// Full screen is a layer OVER the chat (`SessionPanelSide`), not a split
|
|
161
|
+
// at 100%: the split stays exactly where it was, so the chat never
|
|
162
|
+
// squeezes to zero under the opening viewer. Leaving full screen falls
|
|
163
|
+
// through below as an `expandChanged` glide to the split it now wants.
|
|
164
|
+
if (isExpanded) {
|
|
165
|
+
disablePanelTransition();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
160
168
|
const changed = expandChanged || splitChanged || aspectChanged;
|
|
161
169
|
const shouldAnimate = changed && shouldShowPanel && !skipAnimation;
|
|
162
170
|
if (shouldAnimate) {
|
|
@@ -17,7 +17,7 @@ import type { ProjectSession } from '@kortix/sdk';
|
|
|
17
17
|
/** Longest session name kept before it is elided, INCLUDING the ellipsis. */
|
|
18
18
|
export declare const SESSION_TAB_TITLE_MAX_NAME = 60;
|
|
19
19
|
/**
|
|
20
|
-
* `<session name> — Kortix`. An absent name means the session could not be
|
|
20
|
+
* `<session name> — Kortix CompanyOS`. An absent name means the session could not be
|
|
21
21
|
* read, so it gets the explicit unavailable label: never an empty title, never
|
|
22
22
|
* a raw id.
|
|
23
23
|
*/
|
|
@@ -37,7 +37,7 @@ function elide(value) {
|
|
|
37
37
|
return `${value.slice(0, SESSION_TAB_TITLE_MAX_NAME - 1).trimEnd()}…`;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* `<session name> — Kortix`. An absent name means the session could not be
|
|
40
|
+
* `<session name> — Kortix CompanyOS`. An absent name means the session could not be
|
|
41
41
|
* read, so it gets the explicit unavailable label: never an empty title, never
|
|
42
42
|
* a raw id.
|
|
43
43
|
*/
|
|
@@ -2924,6 +2924,9 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
2924
2924
|
focusInOverlay,
|
|
2925
2925
|
isComposing: e.isComposing,
|
|
2926
2926
|
})) {
|
|
2927
|
+
// A press something else consumed breaks the run: three Escapes
|
|
2928
|
+
// that each closed a viewer must never add up to a stop.
|
|
2929
|
+
clearEscHint();
|
|
2927
2930
|
return;
|
|
2928
2931
|
}
|
|
2929
2932
|
e.preventDefault();
|
|
@@ -2961,11 +2964,20 @@ export function SessionChat({ sessionId, projectSessionId, sessionState, project
|
|
|
2961
2964
|
}, 4000);
|
|
2962
2965
|
}
|
|
2963
2966
|
};
|
|
2967
|
+
// A click anywhere also breaks the run. Esc, click an image, Esc, click
|
|
2968
|
+
// the next, Esc is three separate intents, not one stop request — and it
|
|
2969
|
+
// covers any layer whose Escape handler forgets to preventDefault.
|
|
2970
|
+
const onPointerDown = () => {
|
|
2971
|
+
if (escDeadlineRef.current)
|
|
2972
|
+
clearEscHint();
|
|
2973
|
+
};
|
|
2964
2974
|
window.addEventListener('keydown', onKeyDownCapture, true);
|
|
2965
2975
|
window.addEventListener('keydown', onKeyDown);
|
|
2976
|
+
window.addEventListener('pointerdown', onPointerDown, true);
|
|
2966
2977
|
return () => {
|
|
2967
2978
|
window.removeEventListener('keydown', onKeyDownCapture, true);
|
|
2968
2979
|
window.removeEventListener('keydown', onKeyDown);
|
|
2980
|
+
window.removeEventListener('pointerdown', onPointerDown, true);
|
|
2969
2981
|
};
|
|
2970
2982
|
}, [isActiveSessionTab, readOnly, isBusy, canStop, handleStop, clearEscHint, escCount]);
|
|
2971
2983
|
// Reset when session goes idle
|
|
@@ -52,7 +52,7 @@ function WithOneThread({ source, id, rest, pricingLookup, acc, children, }) {
|
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Loads a sub-agent's thread into the store, as the sub-agent's own view does (`useSessionSync`
|
|
55
|
-
* with `savedChild`):
|
|
55
|
+
* with `savedChild`): the session's log, then the runtime's. The section mounts one per requested
|
|
56
56
|
* id (a row that scrolled into view, or "Load all"), outside the tree, so collapsing a row
|
|
57
57
|
* does not unload its children. Reports whether its last runtime read failed.
|
|
58
58
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ToolProps } from '../shared/types.js';
|
|
2
|
-
export declare function ImageGenTool({ call }: ToolProps): import("react").JSX.Element;
|
|
2
|
+
export declare function ImageGenTool({ call }: ToolProps): import("react").JSX.Element | null;
|